From 32666691c21cc91a3d7c7585dad711dc9743fdce Mon Sep 17 00:00:00 2001 From: Mikolaj Boc Date: Thu, 13 Oct 2022 18:24:51 +0200 Subject: Move the window through the title bar element itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compositor is redundant in the process of moving the window. Have the title bar react to move all by itself. Additionally, a clearer structure in the window was introduced. The non-client area has been extracted into a separate class, as was the icon store and free DOM functions used across files. Since it was now easy, made the window maximize/restore on double click on the title element. Fixes: QTBUG-107626 Pick-to: 6.5 Change-Id: Iba7f207e46806ae7162656965892ae5a48ac5ebe Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/wasm/CMakeLists.txt | 3 + .../platforms/wasm/qwasmbase64iconstore.cpp | 40 ++ src/plugins/platforms/wasm/qwasmbase64iconstore.h | 36 ++ src/plugins/platforms/wasm/qwasmcompositor.cpp | 90 +--- src/plugins/platforms/wasm/qwasmcompositor.h | 29 -- src/plugins/platforms/wasm/qwasmcssstyle.cpp | 70 +-- src/plugins/platforms/wasm/qwasmcursor.h | 1 + src/plugins/platforms/wasm/qwasmdom.cpp | 36 ++ src/plugins/platforms/wasm/qwasmdom.h | 29 ++ src/plugins/platforms/wasm/qwasmevent.cpp | 1 + src/plugins/platforms/wasm/qwasmevent.h | 1 + src/plugins/platforms/wasm/qwasmscreen.cpp | 5 +- src/plugins/platforms/wasm/qwasmwindow.cpp | 525 +++------------------ src/plugins/platforms/wasm/qwasmwindow.h | 55 +-- .../platforms/wasm/qwasmwindownonclientarea.cpp | 436 +++++++++++++++++ .../platforms/wasm/qwasmwindownonclientarea.h | 212 +++++++++ 16 files changed, 915 insertions(+), 654 deletions(-) create mode 100644 src/plugins/platforms/wasm/qwasmbase64iconstore.cpp create mode 100644 src/plugins/platforms/wasm/qwasmbase64iconstore.h create mode 100644 src/plugins/platforms/wasm/qwasmdom.cpp create mode 100644 src/plugins/platforms/wasm/qwasmdom.h create mode 100644 src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp create mode 100644 src/plugins/platforms/wasm/qwasmwindownonclientarea.h (limited to 'src/plugins') diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 962c289640..17acc40289 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -15,10 +15,12 @@ qt_internal_add_plugin(QWasmIntegrationPlugin SOURCES main.cpp qwasmaccessibility.cpp qwasmaccessibility.h + qwasmbase64iconstore.cpp qwasmbase64iconstore.h qwasmclipboard.cpp qwasmclipboard.h qwasmcompositor.cpp qwasmcompositor.h qwasmcssstyle.cpp qwasmcssstyle.h qwasmcursor.cpp qwasmcursor.h + qwasmdom.cpp qwasmdom.h qwasmevent.cpp qwasmevent.h qwasmeventdispatcher.cpp qwasmeventdispatcher.h qwasmeventtranslator.cpp qwasmeventtranslator.h @@ -33,6 +35,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmstylepixmaps_p.h qwasmtheme.cpp qwasmtheme.h qwasmwindow.cpp qwasmwindow.h + qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h qwasmdrag.cpp qwasmdrag.h qwasmwindowstack.cpp qwasmwindowstack.h diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp new file mode 100644 index 0000000000..8f05f082ea --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmbase64iconstore.h" + +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(Base64IconStore, globalWasmWindowIconStore); + +Base64IconStore::Base64IconStore() +{ + QString iconSources[static_cast(IconType::Size)] = { + QStringLiteral(":/wasm-window/maximize.svg"), QStringLiteral(":/wasm-window/qtlogo.svg"), + QStringLiteral(":/wasm-window/restore.svg"), QStringLiteral(":/wasm-window/x.svg") + }; + + for (size_t iconType = static_cast(IconType::First); + iconType < static_cast(IconType::Size); ++iconType) { + QFile svgFile(iconSources[static_cast(iconType)]); + if (!svgFile.open(QIODevice::ReadOnly)) + Q_ASSERT(false); // A resource should always be opened. + m_storage[static_cast(iconType)] = svgFile.readAll().toBase64(); + } +} + +Base64IconStore::~Base64IconStore() = default; + +Base64IconStore *Base64IconStore::get() +{ + return globalWasmWindowIconStore(); +} + +std::string_view Base64IconStore::getIcon(IconType type) const +{ + return m_storage[static_cast(type)]; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.h b/src/plugins/platforms/wasm/qwasmbase64iconstore.h new file mode 100644 index 0000000000..6150ea19da --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.h @@ -0,0 +1,36 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMBASE64IMAGESTORE_H +#define QWASMBASE64IMAGESTORE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class Base64IconStore +{ +public: + enum class IconType { + Maximize, + First = Maximize, + QtLogo, + Restore, + X, + Size, + }; + + Base64IconStore(); + ~Base64IconStore(); + + static Base64IconStore *get(); + + std::string_view getIcon(IconType type) const; + +private: + std::string m_storage[static_cast(IconType::Size)]; +}; + +QT_END_NAMESPACE +#endif // QWASMBASE64IMAGESTORE_H diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index f4f2d1b11d..a530cd1f7a 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -44,7 +44,6 @@ EMSCRIPTEN_BINDINGS(qtMouseModule) { QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen), - m_windowManipulation(screen), m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this)), m_eventTranslator(std::make_unique()) { @@ -374,10 +373,9 @@ bool QWasmCompositor::processPointer(const PointerEvent& event) const auto pointInScreen = screen()->mapFromLocal(event.localPoint); QWindow *const targetWindow = ([this, pointInScreen]() -> QWindow * { - auto *targetWindow = m_mouseCaptureWindow != nullptr ? m_mouseCaptureWindow.get() - : m_windowManipulation.operation() == WindowManipulation::Operation::None - ? screen()->compositor()->windowAt(pointInScreen, 5) - : nullptr; + auto *targetWindow = m_mouseCaptureWindow != nullptr + ? m_mouseCaptureWindow.get() + : windowAt(pointInScreen, 5); return targetWindow ? targetWindow : m_lastMouseTargetWindow.get(); })(); @@ -403,20 +401,12 @@ bool QWasmCompositor::processPointer(const PointerEvent& event) if (targetWindow) targetWindow->requestActivate(); - m_windowManipulation.onPointerDown(event, targetWindow); break; } case EventType::PointerUp: { screen()->element().call("releasePointerCapture", event.pointerId); - m_windowManipulation.onPointerUp(event); - break; - } - case EventType::PointerMove: { - m_windowManipulation.onPointerMove(event); - if (m_windowManipulation.operation() != WindowManipulation::Operation::None) - requestUpdate(); break; } case EventType::PointerEnter: @@ -481,80 +471,6 @@ bool QWasmCompositor::deliverEventToTarget(const PointerEvent &event, QWindow *e eventType, event.modifiers); } -QWasmCompositor::WindowManipulation::WindowManipulation(QWasmScreen *screen) - : m_screen(screen) -{ - Q_ASSERT(!!screen); -} - -QWasmCompositor::WindowManipulation::Operation QWasmCompositor::WindowManipulation::operation() const -{ - if (!m_state) - return Operation::None; - return Operation::Move; -} - -void QWasmCompositor::WindowManipulation::onPointerDown( - const PointerEvent& event, QWindow* windowAtPoint) -{ - // Only one operation at a time. - if (operation() != Operation::None) - return; - - if (event.mouseButton != Qt::MouseButton::LeftButton) - return; - - const bool isTargetWindowResizable = - !windowAtPoint->windowStates().testFlag(Qt::WindowMaximized) && - !windowAtPoint->windowStates().testFlag(Qt::WindowFullScreen); - if (!isTargetWindowResizable) - return; - - const bool isTargetWindowBlocked = - QGuiApplicationPrivate::instance()->isWindowBlocked(windowAtPoint); - if (isTargetWindowBlocked) - return; - - if (!asWasmWindow(windowAtPoint)->isPointOnTitle(event.pointInViewport)) - return; - - m_state.reset(new OperationState{ .pointerId = event.pointerId, - .window = windowAtPoint, - .lastPointInScreenCoords = - m_screen->mapFromLocal(event.localPoint) }); -} - -void QWasmCompositor::WindowManipulation::onPointerMove( - const PointerEvent& event) -{ - if (operation() == Operation::None || event.pointerId != m_state->pointerId) - return; - - switch (operation()) { - case Operation::Move: { - const QPoint targetPointClippedToScreen = - m_screen->clipPoint(m_screen->mapFromLocal(event.localPoint)); - const QPoint difference = targetPointClippedToScreen - m_state->lastPointInScreenCoords; - - m_state->lastPointInScreenCoords = targetPointClippedToScreen; - - m_state->window->setPosition(m_state->window->position() + difference); - break; - } - case Operation::None: - Q_ASSERT(0); - break; - } -} - -void QWasmCompositor::WindowManipulation::onPointerUp(const PointerEvent& event) -{ - if (operation() == Operation::None || event.mouseButtons != 0 || event.pointerId != m_state->pointerId) - return; - - m_state.reset(); -} - bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *emKeyEvent) { constexpr bool ProceedToNativeEvent = false; diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h index 17fb25bdd0..2c0ab6ae26 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.h +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -65,34 +65,6 @@ public: void handleBackingStoreFlush(QWindow *window); private: - class WindowManipulation { - public: - enum class Operation { - None, - Move, - }; - - WindowManipulation(QWasmScreen* screen); - - void onPointerDown(const PointerEvent& event, QWindow* windowAtPoint); - void onPointerMove(const PointerEvent& event); - void onPointerUp(const PointerEvent &event); - - Operation operation() const; - - private: - struct OperationState - { - int pointerId; - QPointer window; - QPoint lastPointInScreenCoords; - }; - - QWasmScreen *m_screen; - - std::unique_ptr m_state; - }; - void frame(bool all, const QList &windows); void onTopWindowChanged(); @@ -124,7 +96,6 @@ private: void updateEnabledState(); - WindowManipulation m_windowManipulation; QWasmWindowStack m_windowStack; bool m_isEnabled = true; diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp index f37124c834..2fc2527129 100644 --- a/src/plugins/platforms/wasm/qwasmcssstyle.cpp +++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp @@ -3,6 +3,8 @@ #include "qwasmcssstyle.h" +#include "qwasmbase64iconstore.h" + #include #include @@ -117,6 +119,7 @@ const char *Style = R"css( overflow: hidden; height: 18px; padding-bottom: 4px; + pointer-events: all; } .qt-window.has-title-bar .title-bar { @@ -142,6 +145,10 @@ const char *Style = R"css( display: flex; } +.title-bar div { + pointer-events: none; +} + .title-bar .image-button { width: 18px; height: 18px; @@ -151,7 +158,7 @@ const char *Style = R"css( align-items: center; } -.title-bar .image-button span { +.title-bar .image-button img { width: 10px; height: 10px; user-select: none; @@ -160,19 +167,19 @@ const char *Style = R"css( background-size: 10px 10px; } -.title-bar .image-button span[qt-builtin-image-type=x] { +.title-bar .image-button img[qt-builtin-image-type=x] { background-image: url("data:image/svg+xml;base64,$close_icon"); } -.title-bar .image-button span[qt-builtin-image-type=qt-logo] { +.title-bar .image-button img[qt-builtin-image-type=qt-logo] { background-image: url("qtlogo.svg"); } -.title-bar .image-button span[qt-builtin-image-type=restore] { +.title-bar .image-button img[qt-builtin-image-type=restore] { background-image: url("data:image/svg+xml;base64,$restore_icon"); } -.title-bar .image-button span[qt-builtin-image-type=maximize] { +.title-bar .image-button img[qt-builtin-image-type=maximize] { background-image: url("data:image/svg+xml;base64,$maximize_icon"); } .title-bar .action-button { @@ -180,60 +187,24 @@ const char *Style = R"css( align-self: end; } -.qt-window.blocked .title-bar .action-button { +.qt-window.blocked div { pointer-events: none; } -.title-bar .action-button span { +.title-bar .action-button img { transition: filter 0.08s ease-out; } -.title-bar .action-button:hover span { +.title-bar .action-button:hover img { filter: invert(0.45); } -.title-bar .action-button:active span { +.title-bar .action-button:active img { filter: invert(0.6); } )css"; -class Base64IconStore -{ -public: - enum class IconType { - Maximize, - First = Maximize, - QtLogo, - Restore, - X, - Size, - }; - - Base64IconStore() - { - QString iconSources[static_cast(IconType::Size)] = { - QStringLiteral(":/wasm-window/maximize.svg"), - QStringLiteral(":/wasm-window/qtlogo.svg"), QStringLiteral(":/wasm-window/restore.svg"), - QStringLiteral(":/wasm-window/x.svg") - }; - - for (size_t iconType = static_cast(IconType::First); - iconType < static_cast(IconType::Size); ++iconType) { - QFile svgFile(iconSources[static_cast(iconType)]); - if (!svgFile.open(QIODevice::ReadOnly)) - Q_ASSERT(false); // A resource should always be opened. - m_storage[static_cast(iconType)] = svgFile.readAll().toBase64(); - } - } - ~Base64IconStore() = default; - - std::string_view getIcon(IconType type) const { return m_storage[static_cast(type)]; } - -private: - std::string m_storage[static_cast(IconType::Size)]; -}; - void replace(std::string &str, const std::string &from, const std::string_view &to) { str.replace(str.find(from), from.length(), to); @@ -242,13 +213,14 @@ void replace(std::string &str, const std::string &from, const std::string_view & emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent) { - Base64IconStore store; auto document = parent["ownerDocument"]; auto screenStyle = document.call("createElement", emscripten::val("style")); auto text = std::string(Style); - replace(text, "$close_icon", store.getIcon(Base64IconStore::IconType::X)); - replace(text, "$restore_icon", store.getIcon(Base64IconStore::IconType::Restore)); - replace(text, "$maximize_icon", store.getIcon(Base64IconStore::IconType::Maximize)); + + using IconType = Base64IconStore::IconType; + replace(text, "$close_icon", Base64IconStore::get()->getIcon(IconType::X)); + replace(text, "$restore_icon", Base64IconStore::get()->getIcon(IconType::Restore)); + replace(text, "$maximize_icon", Base64IconStore::get()->getIcon(IconType::Maximize)); screenStyle.set("textContent", text); return screenStyle; diff --git a/src/plugins/platforms/wasm/qwasmcursor.h b/src/plugins/platforms/wasm/qwasmcursor.h index 4a5cb23bf4..bb3e2a1346 100644 --- a/src/plugins/platforms/wasm/qwasmcursor.h +++ b/src/plugins/platforms/wasm/qwasmcursor.h @@ -5,6 +5,7 @@ #define QWASMCURSOR_H #include + QT_BEGIN_NAMESPACE class QWasmCursor : public QPlatformCursor diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp new file mode 100644 index 0000000000..1573b5bf9c --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmdom.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmdom.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace dom { +void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag) +{ + if (flag) { + element["classList"].call("add", emscripten::val(std::move(cssClassName))); + return; + } + + element["classList"].call("remove", emscripten::val(std::move(cssClassName))); +} + +QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point) +{ + auto sourceBoundingRect = + QRectF::fromDOMRect(source.call("getBoundingClientRect")); + auto targetBoundingRect = + QRectF::fromDOMRect(target.call("getBoundingClientRect")); + + auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft(); + return (point + offset).toPoint(); +} +} // namespace dom + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmdom.h b/src/plugins/platforms/wasm/qwasmdom.h new file mode 100644 index 0000000000..92f710a13d --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmdom.h @@ -0,0 +1,29 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMDOM_H +#define QWASMDOM_H + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QPoint; + +namespace dom { +inline emscripten::val document() +{ + return emscripten::val::global("document"); +} + +void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag); + +QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point); +} // namespace dom + +QT_END_NAMESPACE +#endif // QWASMDOM_H diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp index 0e43a381bf..7276087033 100644 --- a/src/plugins/platforms/wasm/qwasmevent.cpp +++ b/src/plugins/platforms/wasm/qwasmevent.cpp @@ -39,6 +39,7 @@ std::optional PointerEvent::fromWeb(emscripten::val event) return std::nullopt; ret.type = *eventType; + ret.currentTarget = event["currentTarget"]; ret.pointerType = event["pointerType"].as() == "mouse" ? PointerType::Mouse : PointerType::Other; ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as()); diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h index 26b6b5f22a..7fe68ce6c8 100644 --- a/src/plugins/platforms/wasm/qwasmevent.h +++ b/src/plugins/platforms/wasm/qwasmevent.h @@ -110,6 +110,7 @@ QFlags getForEvent( struct Q_CORE_EXPORT Event { EventType type; + emscripten::val currentTarget = emscripten::val::undefined(); }; struct Q_CORE_EXPORT MouseEvent : public Event diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index 366d495089..e50afe652f 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -230,9 +230,8 @@ QPoint QWasmScreen::mapFromLocal(const QPoint &p) const QPoint QWasmScreen::clipPoint(const QPoint &p) const { - return QPoint( - std::max(screen()->geometry().left(), std::min(screen()->geometry().right(), p.x())), - std::max(screen()->geometry().top(), std::min(screen()->geometry().bottom(), p.y()))); + return QPoint(qBound(screen()->geometry().left(), p.x(), screen()->geometry().right()), + qBound(screen()->geometry().top(), p.y(), screen()->geometry().bottom())); } void QWasmScreen::invalidateSize() diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index e38335f81c..43fc151d72 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -9,6 +9,8 @@ #include #include +#include "qwasmbase64iconstore.h" +#include "qwasmdom.h" #include "qwasmwindow.h" #include "qwasmscreen.h" #include "qwasmstylepixmaps_p.h" @@ -26,418 +28,24 @@ QT_BEGIN_NAMESPACE Q_GUI_EXPORT int qt_defaultDpiX(); -namespace { -enum class IconType { - Maximize, - First = Maximize, - QtLogo, - Restore, - X, - Size, -}; - -void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag) -{ - if (flag) { - element["classList"].call("add", emscripten::val(std::move(cssClassName))); - return; - } - - element["classList"].call("remove", emscripten::val(std::move(cssClassName))); -} - -} // namespace - -class QWasmWindow::Resizer -{ -public: - class ResizerElement - { - public: - static constexpr const char *cssClassNameForEdges(Qt::Edges edges) - { - switch (edges) { - case Qt::TopEdge | Qt::LeftEdge:; - return "nw"; - case Qt::TopEdge: - return "n"; - case Qt::TopEdge | Qt::RightEdge: - return "ne"; - case Qt::LeftEdge: - return "w"; - case Qt::RightEdge: - return "e"; - case Qt::BottomEdge | Qt::LeftEdge: - return "sw"; - case Qt::BottomEdge: - return "s"; - case Qt::BottomEdge | Qt::RightEdge: - return "se"; - default: - Q_ASSERT(false); // notreached - return ""; - } - } - - ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer) - : m_element(emscripten::val::global("document") - .call("createElement", emscripten::val("div"))), - m_edges(edges), - m_resizer(resizer) - { - Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null"); - - m_element["classList"].call("add", emscripten::val("resize-outline")); - m_element["classList"].call("add", emscripten::val(cssClassNameForEdges(edges))); - - parentElement.call("appendChild", m_element); - - m_mouseDownEvent = std::make_unique( - m_element, "pointerdown", [this](emscripten::val event) { - if (!onPointerDown(*PointerEvent::fromWeb(event))) - return; - m_resizer->onInteraction(); - event.call("preventDefault"); - event.call("stopPropagation"); - }); - m_mouseDragEvent = std::make_unique( - m_element, "pointermove", [this](emscripten::val event) { - if (onPointerMove(*PointerEvent::fromWeb(event))) { - event.call("preventDefault"); - event.call("stopPropagation"); - } - }); - m_mouseUpEvent = std::make_unique( - m_element, "pointerup", [this](emscripten::val event) { - if (onPointerUp(*PointerEvent::fromWeb(event))) { - event.call("preventDefault"); - event.call("stopPropagation"); - } - }); - } - - ~ResizerElement() - { - m_element["parentElement"].call("removeChild", m_element); - } - ResizerElement(const ResizerElement &other) = delete; - ResizerElement(ResizerElement &&other) = default; - ResizerElement &operator=(const ResizerElement &other) = delete; - ResizerElement &operator=(ResizerElement &&other) = delete; - - bool onPointerDown(const PointerEvent &event) - { - if (event.pointerType != PointerType::Mouse) - return false; - - m_element.call("setPointerCapture", event.pointerId); - m_capturedPointerId = event.pointerId; - - m_resizer->startResize(m_edges, event.pointInPage); - return true; - } - - bool onPointerMove(const PointerEvent &event) - { - if (m_capturedPointerId != event.pointerId) - return false; - - m_resizer->continueResize(event.pointInPage); - return true; - } - - bool onPointerUp(const PointerEvent &event) - { - if (m_capturedPointerId != event.pointerId) - return false; - - m_resizer->finishResize(); - m_element.call("releasePointerCapture", event.pointerId); - m_capturedPointerId = -1; - return true; - } - - private: - emscripten::val m_element; - - int m_capturedPointerId = -1; - - const Qt::Edges m_edges; - - Resizer *m_resizer; - - std::unique_ptr m_mouseDownEvent; - std::unique_ptr m_mouseDragEvent; - std::unique_ptr m_mouseUpEvent; - }; - - using ClickCallback = std::function; - - Resizer(QWasmWindow *window, emscripten::val parentElement) : m_window(window) - { - Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null"); - - constexpr std::array ResizeEdges = { Qt::TopEdge | Qt::LeftEdge, - Qt::TopEdge, - Qt::TopEdge | Qt::RightEdge, - Qt::LeftEdge, - Qt::RightEdge, - Qt::BottomEdge | Qt::LeftEdge, - Qt::BottomEdge, - Qt::BottomEdge | Qt::RightEdge }; - std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), - std::back_inserter(m_elements), [parentElement, this](int edges) { - return std::make_unique(parentElement, - Qt::Edges::fromInt(edges), this); - }); - } - - ~Resizer() = default; - -private: - void onInteraction() { m_window->onInteraction(); } - - void startResize(Qt::Edges resizeEdges, const QPoint &origin) - { - Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress"); - - const QWindow *window = m_window->window(); - // TODO(mikolajboc): Implement system resize - // .m_originInScreenCoords = m_systemDragInitData.lastMouseMovePoint, - m_currentResizeData.reset(new ResizeData{ - .edges = resizeEdges, - .originInScreenCoords = origin, - .initialWindowBounds = window->geometry(), - .minShrink = QPoint(window->minimumWidth() - window->geometry().width(), - window->minimumHeight() - window->geometry().height()), - .maxGrow = QPoint(window->maximumWidth() - window->geometry().width(), - window->maximumHeight() - window->geometry().height()) }); - } - - void continueResize(const QPoint &point) - { - const auto amount = point - m_currentResizeData->originInScreenCoords; - const QPoint cappedGrowVector( - std::min(m_currentResizeData->maxGrow.x(), - std::max(m_currentResizeData->minShrink.x(), - (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x() - : (m_currentResizeData->edges & Qt::Edge::RightEdge) - ? amount.x() - : 0)), - std::min(m_currentResizeData->maxGrow.y(), - std::max(m_currentResizeData->minShrink.y(), - (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y() - : (m_currentResizeData->edges & Qt::Edge::BottomEdge) - ? amount.y() - : 0))); - - auto bounds = m_currentResizeData->initialWindowBounds.adjusted( - (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0, - (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0, - (m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0, - (m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0); - bounds.setY(std::max(m_window->screen()->geometry().y() + m_window->frameMargins().top(), - bounds.y())); - m_window->setGeometry(std::move(bounds)); - } - - void finishResize() - { - Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress"); - m_currentResizeData.reset(); - } - - struct ResizeData - { - Qt::Edges edges = Qt::Edges::fromInt(0); - QPoint originInScreenCoords; - QRect initialWindowBounds; - QPoint minShrink; - QPoint maxGrow; - }; - std::unique_ptr m_currentResizeData; - - QWasmWindow *m_window; - std::vector> m_elements; -}; - -class QWasmWindow::WebImageButton -{ -public: - class Callbacks - { - public: - Callbacks() = default; - Callbacks(std::function onInteraction, std::function onClick) - : m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick)) - { - Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO, - "Both callbacks need to be either null or non-null"); - } - ~Callbacks() = default; - - Callbacks(const Callbacks &) = delete; - Callbacks(Callbacks &&) = default; - Callbacks &operator=(const Callbacks &) = delete; - Callbacks &operator=(Callbacks &&) = default; - - operator bool() const { return !!m_onInteraction; } - - void onInteraction() { return m_onInteraction(); } - void onClick() { return m_onClick(); } - - private: - std::function m_onInteraction; - std::function m_onClick; - }; - - WebImageButton() - : m_containerElement( - emscripten::val::global("document") - .call("createElement", emscripten::val("div"))), - m_imageHolderElement( - emscripten::val::global("document") - .call("createElement", emscripten::val("span"))) - { - m_imageHolderElement.set("draggable", false); - - m_containerElement["classList"].call("add", emscripten::val("image-button")); - m_containerElement.call("appendChild", m_imageHolderElement); - } - - ~WebImageButton() = default; - - void setCallbacks(Callbacks callbacks) - { - if (callbacks) { - if (!m_webClickEventCallback) { - m_webMouseDownEventCallback = std::make_unique( - m_containerElement, "mousedown", [this](emscripten::val event) { - event.call("preventDefault"); - event.call("stopPropagation"); - m_callbacks.onInteraction(); - }); - m_webClickEventCallback = std::make_unique( - m_containerElement, "click", - [this](emscripten::val) { m_callbacks.onClick(); }); - } - } else { - m_webMouseDownEventCallback.reset(); - m_webClickEventCallback.reset(); - } - syncCSSClassWith(m_containerElement, "action-button", !!callbacks); - m_callbacks = std::move(callbacks); - } - - void setImage(std::string_view imageData, std::string_view format) - { - m_imageHolderElement.call("removeAttribute", - emscripten::val("qt-builtin-image-type")); - m_imageHolderElement["style"].set("backgroundImage", - "url('data:image/" + std::string(format) + ";base64," - + std::string(imageData) + "')"); - } - - void setImage(IconType type) - { - m_imageHolderElement["style"].set("backgroundImage", emscripten::val::undefined()); - const auto imageType = ([type]() { - switch (type) { - case IconType::QtLogo: - return "qt-logo"; - case IconType::X: - return "x"; - case IconType::Restore: - return "restore"; - case IconType::Maximize: - return "maximize"; - default: - return "err"; - } - })(); - m_imageHolderElement.call("setAttribute", emscripten::val("qt-builtin-image-type"), - emscripten::val(imageType)); - } - - void setVisible(bool visible) - { - m_containerElement["style"].set("display", visible ? "flex" : "none"); - } - - emscripten::val htmlElement() const { return m_containerElement; } - emscripten::val imageElement() const { return m_imageHolderElement; } - -private: - emscripten::val m_containerElement; - emscripten::val m_imageHolderElement; - std::unique_ptr m_webMouseMoveEventCallback; - std::unique_ptr m_webMouseDownEventCallback; - std::unique_ptr m_webClickEventCallback; - - Callbacks m_callbacks; -}; - QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore) : QPlatformWindow(w), m_window(w), m_compositor(compositor), m_backingStore(backingStore), - m_document(emscripten::val::global("document")), + m_document(dom::document()), m_qtWindow(m_document.call("createElement", emscripten::val("div"))), m_windowContents(m_document.call("createElement", emscripten::val("div"))), - m_titleBar(m_document.call("createElement", emscripten::val("div"))), - m_label(m_document.call("createElement", emscripten::val("div"))), m_canvasContainer(m_document.call("createElement", emscripten::val("div"))), m_canvas(m_document.call("createElement", emscripten::val("canvas"))) { m_qtWindow.set("className", "qt-window"); m_qtWindow["style"].set("display", std::string("none")); - m_qtWindow.call("appendChild", m_windowContents); + m_nonClientArea = std::make_unique(this, m_qtWindow); + m_nonClientArea->titleBar()->setTitle(window()->title()); - m_resizer = std::make_unique(this, m_qtWindow); - - m_icon = std::make_unique(); - m_icon->setImage(IconType::QtLogo); - - m_titleBar.call("appendChild", m_icon->htmlElement()); - m_titleBar.set("className", "title-bar"); - - auto spacer = m_document.call("createElement", emscripten::val("div")); - spacer["style"].set("width", "4px"); - m_titleBar.call("appendChild", spacer); - - m_label.set("innerText", emscripten::val(window()->title().toStdString())); - m_label.set("className", "window-name"); - - m_titleBar.call("appendChild", m_label); - - spacer = m_document.call("createElement", emscripten::val("div")); - spacer.set("className", "spacer"); - m_titleBar.call("appendChild", spacer); - - m_restore = std::make_unique(); - m_restore->setImage(IconType::Restore); - m_restore->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, - [this]() { onRestoreClicked(); })); - - m_titleBar.call("appendChild", m_restore->htmlElement()); - - m_maximize = std::make_unique(); - m_maximize->setImage(IconType::Maximize); - m_maximize->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, - [this]() { onMaximizeClicked(); })); - - m_titleBar.call("appendChild", m_maximize->htmlElement()); - - m_close = std::make_unique(); - m_close->setImage(IconType::X); - m_close->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); }, - [this]() { onCloseClicked(); })); - - m_titleBar.call("appendChild", m_close->htmlElement()); - - m_windowContents.call("appendChild", m_titleBar); + m_qtWindow.call("appendChild", m_windowContents); m_canvas["classList"].call("add", emscripten::val("qt-window-content")); @@ -468,6 +76,55 @@ QWasmWindow::~QWasmWindow() emscripten_cancel_animation_frame(m_requestAnimationFrameId); } +void QWasmWindow::onRestoreClicked() +{ + window()->setWindowState(Qt::WindowNoState); +} + +void QWasmWindow::onMaximizeClicked() +{ + window()->setWindowState(Qt::WindowMaximized); +} + +void QWasmWindow::onToggleMaximized() +{ + window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState + : Qt::WindowMaximized); +} + +void QWasmWindow::onCloseClicked() +{ + window()->close(); +} + +void QWasmWindow::onNonClientAreaInteraction() +{ + if (!isActive()) + requestActivateWindow(); +} + +bool QWasmWindow::onNonClientEvent(const PointerEvent &event) +{ + QPoint pointInScreen = platformScreen()->mapFromLocal( + dom::mapPoint(event.currentTarget, platformScreen()->element(), event.localPoint)); + return QWindowSystemInterface::handleMouseEvent( + window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen), + pointInScreen, event.mouseButtons, event.mouseButton, ([event]() { + switch (event.type) { + case EventType::PointerDown: + return QEvent::NonClientAreaMouseButtonPress; + case EventType::PointerUp: + return QEvent::NonClientAreaMouseButtonRelease; + case EventType::PointerMove: + return QEvent::NonClientAreaMouseMove; + default: + Q_ASSERT(false); // notreached + return QEvent::None; + } + })(), + event.modifiers); +} + void QWasmWindow::destroy() { m_qtWindow["parentElement"].call("removeChild", m_qtWindow); @@ -541,6 +198,8 @@ void QWasmWindow::setGeometry(const QRect &rect) screenGeometry.y() + margins.top())); return result; })(); + m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width()); + const auto frameRect = clientAreaRect .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()) @@ -591,12 +250,13 @@ bool QWasmWindow::isVisible() const QMargins QWasmWindow::frameMargins() const { - const auto border = borderMargins(); - const auto titleBarBounds = - QRectF::fromDOMRect(m_titleBar.call("getBoundingClientRect")); - - return QMarginsF(border.left(), border.top() + titleBarBounds.height(), border.right(), - border.bottom()) + const auto frameRect = + QRectF::fromDOMRect(m_qtWindow.call("getBoundingClientRect")); + const auto canvasRect = + QRectF::fromDOMRect(m_windowContents.call("getBoundingClientRect")); + return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(), + frameRect.right() - canvasRect.right(), + frameRect.bottom() - canvasRect.bottom()) .toMargins(); } @@ -633,44 +293,6 @@ bool QWasmWindow::startSystemResize(Qt::Edges) return false; } -void QWasmWindow::onRestoreClicked() -{ - window()->setWindowState(Qt::WindowNoState); -} - -void QWasmWindow::onMaximizeClicked() -{ - window()->setWindowState(Qt::WindowMaximized); -} - -void QWasmWindow::onCloseClicked() -{ - window()->close(); -} - -void QWasmWindow::onInteraction() -{ - if (!isActive()) - requestActivateWindow(); -} - -QMarginsF QWasmWindow::borderMargins() const -{ - const auto frameRect = - QRectF::fromDOMRect(m_qtWindow.call("getBoundingClientRect")); - const auto canvasRect = - QRectF::fromDOMRect(m_windowContents.call("getBoundingClientRect")); - return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(), - frameRect.right() - canvasRect.right(), - frameRect.bottom() - canvasRect.bottom()); -} - -bool QWasmWindow::isPointOnTitle(QPoint globalPoint) const -{ - return QRectF::fromDOMRect(m_titleBar.call("getBoundingClientRect")) - .contains(globalPoint); -} - void QWasmWindow::invalidate() { m_compositor->requestUpdateWindow(this); @@ -678,13 +300,13 @@ void QWasmWindow::invalidate() void QWasmWindow::onActivationChanged(bool active) { - syncCSSClassWith(m_qtWindow, "inactive", !active); + dom::syncCSSClassWith(m_qtWindow, "inactive", !active); } void QWasmWindow::setWindowFlags(Qt::WindowFlags flags) { m_flags = flags; - syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); + dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); } void QWasmWindow::setWindowState(Qt::WindowStates newState) @@ -709,7 +331,7 @@ void QWasmWindow::setWindowState(Qt::WindowStates newState) void QWasmWindow::setWindowTitle(const QString &title) { - m_label.set("innerText", emscripten::val(title.toStdString())); + m_nonClientArea->titleBar()->setTitle(title); } void QWasmWindow::setWindowIcon(const QIcon &icon) @@ -717,14 +339,15 @@ void QWasmWindow::setWindowIcon(const QIcon &icon) const auto dpi = screen()->devicePixelRatio(); auto pixmap = icon.pixmap(10 * dpi, 10 * dpi); if (pixmap.isNull()) { - m_icon->setImage(IconType::QtLogo); + m_nonClientArea->titleBar()->setIcon( + Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml"); return; } QByteArray bytes; QBuffer buffer(&bytes); pixmap.save(&buffer, "png"); - m_icon->setImage(bytes.toBase64().toStdString(), "png"); + m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(), "png"); } void QWasmWindow::applyWindowState() @@ -740,11 +363,11 @@ void QWasmWindow::applyWindowState() else newGeom = normalGeometry(); - syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); - syncCSSClassWith(m_qtWindow, "maximized", isMaximized); + dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar()); + dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized); - m_restore->setVisible(isMaximized); - m_maximize->setVisible(!isMaximized); + m_nonClientArea->titleBar()->setRestoreVisible(isMaximized); + m_nonClientArea->titleBar()->setMaximizeVisible(!isMaximized); if (isVisible()) QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState); diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 8359391825..d5a902e8b0 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -10,6 +10,7 @@ #include "qwasmbackingstore.h" #include "qwasmscreen.h" #include "qwasmcompositor.h" +#include "qwasmwindownonclientarea.h" #include #include "QtGui/qopenglcontext.h" @@ -24,21 +25,26 @@ class QWasmWindow final : public QPlatformWindow public: QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore); ~QWasmWindow() final; - void destroy(); - - void initialize() override; + void destroy(); void paint(); void setZOrder(int order); void onActivationChanged(bool active); + bool isVisible() const; + void onNonClientAreaInteraction(); + void onRestoreClicked(); + void onMaximizeClicked(); + void onToggleMaximized(); + void onCloseClicked(); + bool onNonClientEvent(const PointerEvent &event); + + // QPlatformWindow: + void initialize() override; void setGeometry(const QRect &) override; void setVisible(bool visible) override; - bool isVisible() const; QMargins frameMargins() const override; - WId winId() const override; - void propagateSizeHints() override; void raise() override; void lower() override; @@ -46,24 +52,19 @@ public: qreal devicePixelRatio() const override; void requestUpdate() override; void requestActivateWindow() override; - - QWasmScreen *platformScreen() const; - void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; } - QWasmBackingStore *backingStore() const { return m_backingStore; } - QWindow *window() const { return m_window; } - - bool startSystemResize(Qt::Edges edges) final; - - bool isPointOnTitle(QPoint point) const; - void setWindowFlags(Qt::WindowFlags flags) override; void setWindowState(Qt::WindowStates state) override; void setWindowTitle(const QString &title) override; void setWindowIcon(const QIcon &icon) override; - void applyWindowState(); bool setKeyboardGrabEnabled(bool) override { return false; } bool setMouseGrabEnabled(bool grab) final; bool windowEvent(QEvent *event) final; + bool startSystemResize(Qt::Edges edges) final; + + QWasmScreen *platformScreen() const; + void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; } + QWasmBackingStore *backingStore() const { return m_backingStore; } + QWindow *window() const { return m_window; } std::string canvasSelector() const; emscripten::val context2d() { return m_context2d; } @@ -71,18 +72,9 @@ public: private: friend class QWasmScreen; - class Resizer; - class WebImageButton; - - QMarginsF borderMargins() const; - - void onRestoreClicked(); - void onMaximizeClicked(); - void onCloseClicked(); - void onInteraction(); - void invalidate(); bool hasTitleBar() const; + void applyWindowState(); QWindow *m_window = nullptr; QWasmCompositor *m_compositor = nullptr; @@ -92,18 +84,11 @@ private: emscripten::val m_document; emscripten::val m_qtWindow; emscripten::val m_windowContents; - emscripten::val m_titleBar; - emscripten::val m_label; emscripten::val m_canvasContainer; emscripten::val m_canvas; emscripten::val m_context2d = emscripten::val::undefined(); - std::unique_ptr m_resizer; - - std::unique_ptr m_close; - std::unique_ptr m_maximize; - std::unique_ptr m_restore; - std::unique_ptr m_icon; + std::unique_ptr m_nonClientArea; Qt::WindowStates m_state = Qt::WindowNoState; Qt::WindowStates m_previousWindowState = Qt::WindowNoState; diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp new file mode 100644 index 0000000000..170cbbfa31 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp @@ -0,0 +1,436 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qwasmwindownonclientarea.h" + +#include "qwasmbase64iconstore.h" +#include "qwasmdom.h" +#include "qwasmevent.h" +#include "qwasmintegration.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +WebImageButton::Callbacks::Callbacks() = default; +WebImageButton::Callbacks::Callbacks(std::function onInteraction, + std::function onClick) + : m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick)) +{ + Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO, + "Both callbacks need to be either null or non-null"); +} +WebImageButton::Callbacks::~Callbacks() = default; + +WebImageButton::Callbacks::Callbacks(Callbacks &&) = default; +WebImageButton::Callbacks &WebImageButton::Callbacks::operator=(Callbacks &&) = default; + +void WebImageButton::Callbacks::onInteraction() +{ + return m_onInteraction(); +} + +void WebImageButton::Callbacks::onClick() +{ + return m_onClick(); +} + +WebImageButton::WebImageButton() + : m_containerElement( + dom::document().call("createElement", emscripten::val("div"))), + m_imgElement(dom::document().call("createElement", emscripten::val("img"))) +{ + m_imgElement.set("draggable", false); + + m_containerElement["classList"].call("add", emscripten::val("image-button")); + m_containerElement.call("appendChild", m_imgElement); +} + +WebImageButton::~WebImageButton() = default; + +void WebImageButton::setCallbacks(Callbacks callbacks) +{ + if (callbacks) { + if (!m_webClickEventCallback) { + m_webMouseDownEventCallback = std::make_unique( + m_containerElement, "mousedown", [this](emscripten::val event) { + event.call("preventDefault"); + event.call("stopPropagation"); + m_callbacks.onInteraction(); + }); + m_webClickEventCallback = std::make_unique( + m_containerElement, "click", + [this](emscripten::val) { m_callbacks.onClick(); }); + } + } else { + m_webMouseDownEventCallback.reset(); + m_webClickEventCallback.reset(); + } + dom::syncCSSClassWith(m_containerElement, "action-button", !!callbacks); + m_callbacks = std::move(callbacks); +} + +void WebImageButton::setImage(std::string_view imageData, std::string_view format) +{ + m_imgElement.set("src", + "data:image/" + std::string(format) + ";base64," + std::string(imageData)); +} + +void WebImageButton::setVisible(bool visible) +{ + m_containerElement["style"].set("display", visible ? "flex" : "none"); +} + +Resizer::ResizerElement::ResizerElement(emscripten::val parentElement, Qt::Edges edges, + Resizer *resizer) + : m_element(dom::document().call("createElement", emscripten::val("div"))), + m_edges(edges), + m_resizer(resizer) +{ + Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null"); + + m_element["classList"].call("add", emscripten::val("resize-outline")); + m_element["classList"].call("add", emscripten::val(cssClassNameForEdges(edges))); + + parentElement.call("appendChild", m_element); + + m_mouseDownEvent = std::make_unique( + m_element, "pointerdown", [this](emscripten::val event) { + if (!onPointerDown(*PointerEvent::fromWeb(event))) + return; + m_resizer->onInteraction(); + event.call("preventDefault"); + event.call("stopPropagation"); + }); + m_mouseDragEvent = std::make_unique( + m_element, "pointermove", [this](emscripten::val event) { + if (onPointerMove(*PointerEvent::fromWeb(event))) { + event.call("preventDefault"); + event.call("stopPropagation"); + } + }); + m_mouseUpEvent = std::make_unique( + m_element, "pointerup", [this](emscripten::val event) { + if (onPointerUp(*PointerEvent::fromWeb(event))) { + event.call("preventDefault"); + event.call("stopPropagation"); + } + }); +} + +Resizer::ResizerElement::~ResizerElement() +{ + m_element["parentElement"].call("removeChild", m_element); +} + +Resizer::ResizerElement::ResizerElement(ResizerElement &&other) = default; + +bool Resizer::ResizerElement::onPointerDown(const PointerEvent &event) +{ + if (event.pointerType != PointerType::Mouse) + return false; + + m_element.call("setPointerCapture", event.pointerId); + m_capturedPointerId = event.pointerId; + + m_resizer->startResize(m_edges, event); + return true; +} + +bool Resizer::ResizerElement::onPointerMove(const PointerEvent &event) +{ + if (m_capturedPointerId != event.pointerId) + return false; + + m_resizer->continueResize(event); + return true; +} + +bool Resizer::ResizerElement::onPointerUp(const PointerEvent &event) +{ + if (m_capturedPointerId != event.pointerId) + return false; + + m_resizer->finishResize(); + m_element.call("releasePointerCapture", event.pointerId); + m_capturedPointerId = -1; + return true; +} + +Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement) + : m_window(window), m_windowElement(parentElement) +{ + Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null"); + + constexpr std::array ResizeEdges = { Qt::TopEdge | Qt::LeftEdge, + Qt::TopEdge, + Qt::TopEdge | Qt::RightEdge, + Qt::LeftEdge, + Qt::RightEdge, + Qt::BottomEdge | Qt::LeftEdge, + Qt::BottomEdge, + Qt::BottomEdge | Qt::RightEdge }; + std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), std::back_inserter(m_elements), + [parentElement, this](int edges) { + return std::make_unique(parentElement, + Qt::Edges::fromInt(edges), this); + }); +} + +Resizer::~Resizer() = default; + +void Resizer::onInteraction() +{ + m_window->onNonClientAreaInteraction(); +} + +void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event) +{ + Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress"); + + m_currentResizeData.reset(new ResizeData{ + .edges = resizeEdges, + .originInScreenCoords = dom::mapPoint( + event.currentTarget, m_window->platformScreen()->element(), event.localPoint), + }); + + const auto *window = m_window->window(); + m_currentResizeData->minShrink = QPoint(window->minimumWidth() - window->geometry().width(), + window->minimumHeight() - window->geometry().height()); + + const auto frameRect = + QRectF::fromDOMRect(m_windowElement.call("getBoundingClientRect")); + const auto screenRect = QRectF::fromDOMRect( + m_window->platformScreen()->element().call("getBoundingClientRect")); + + const int maxGrowTop = frameRect.top() - screenRect.top(); + + m_currentResizeData->maxGrow = + QPoint(window->maximumWidth() - window->geometry().width(), + std::min(resizeEdges & Qt::Edge::TopEdge ? maxGrowTop : INT_MAX, + window->maximumHeight() - window->geometry().height())); + + m_currentResizeData->initialBounds = window->geometry(); + + // TODO(mikolajboc): Implement system resize + // .m_originInScreenCoords = m_systemDragInitData.lastMouseMovePoint, +} + +void Resizer::continueResize(const PointerEvent &event) +{ + const auto pointInScreen = dom::mapPoint( + event.currentTarget, m_window->platformScreen()->element(), event.localPoint); + const auto amount = pointInScreen - m_currentResizeData->originInScreenCoords; + const QPoint cappedGrowVector( + std::min(m_currentResizeData->maxGrow.x(), + std::max(m_currentResizeData->minShrink.x(), + (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x() + : (m_currentResizeData->edges & Qt::Edge::RightEdge) + ? amount.x() + : 0)), + std::min(m_currentResizeData->maxGrow.y(), + std::max(m_currentResizeData->minShrink.y(), + (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y() + : (m_currentResizeData->edges & Qt::Edge::BottomEdge) + ? amount.y() + : 0))); + + auto bounds = m_currentResizeData->initialBounds.adjusted( + (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0, + (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0, + (m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0, + (m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0); + + m_window->window()->setGeometry(bounds); +} + +void Resizer::finishResize() +{ + Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress"); + m_currentResizeData.reset(); +} + +TitleBar::TitleBar(QWasmWindow *window, emscripten::val parentElement) + : m_window(window), + m_element(dom::document().call("createElement", emscripten::val("div"))), + m_label(dom::document().call("createElement", emscripten::val("div"))) +{ + m_icon = std::make_unique(); + m_icon->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml"); + m_element.call("appendChild", m_icon->htmlElement()); + m_element.set("className", "title-bar"); + + auto spacer = dom::document().call("createElement", emscripten::val("div")); + spacer["style"].set("width", "4px"); + m_element.call("appendChild", spacer); + + m_label.set("className", "window-name"); + + m_element.call("appendChild", m_label); + + spacer = dom::document().call("createElement", emscripten::val("div")); + spacer.set("className", "spacer"); + m_element.call("appendChild", spacer); + + m_restore = std::make_unique(); + m_restore->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Restore), + "svg+xml"); + m_restore->setCallbacks( + WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); }, + [this]() { m_window->onRestoreClicked(); })); + + m_element.call("appendChild", m_restore->htmlElement()); + + m_maximize = std::make_unique(); + m_maximize->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Maximize), + "svg+xml"); + m_maximize->setCallbacks( + WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); }, + [this]() { m_window->onMaximizeClicked(); })); + + m_element.call("appendChild", m_maximize->htmlElement()); + + m_close = std::make_unique(); + m_close->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::X), "svg+xml"); + m_close->setCallbacks( + WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); }, + [this]() { m_window->onCloseClicked(); })); + + m_element.call("appendChild", m_close->htmlElement()); + + parentElement.call("appendChild", m_element); + + m_mouseDownEvent = std::make_unique( + m_element, "pointerdown", [this](emscripten::val event) { + if (!onPointerDown(*PointerEvent::fromWeb(event))) + return; + m_window->onNonClientAreaInteraction(); + event.call("preventDefault"); + event.call("stopPropagation"); + }); + m_mouseDragEvent = std::make_unique( + m_element, "pointermove", [this](emscripten::val event) { + if (onPointerMove(*PointerEvent::fromWeb(event))) { + event.call("preventDefault"); + event.call("stopPropagation"); + } + }); + m_mouseUpEvent = std::make_unique( + m_element, "pointerup", [this](emscripten::val event) { + if (onPointerUp(*PointerEvent::fromWeb(event))) { + event.call("preventDefault"); + event.call("stopPropagation"); + } + }); + m_doubleClickEvent = std::make_unique( + m_element, "dblclick", [this](emscripten::val event) { + if (onDoubleClick()) { + event.call("preventDefault"); + event.call("stopPropagation"); + } + }); +} + +TitleBar::~TitleBar() +{ + m_element["parentElement"].call("removeChild", m_element); +} + +void TitleBar::setTitle(const QString &title) +{ + m_label.set("innerText", emscripten::val(title.toStdString())); +} + +void TitleBar::setRestoreVisible(bool visible) +{ + m_restore->setVisible(visible); +} + +void TitleBar::setMaximizeVisible(bool visible) +{ + m_maximize->setVisible(visible); +} + +void TitleBar::setIcon(std::string_view imageData, std::string_view format) +{ + m_icon->setImage(imageData, format); +} + +void TitleBar::setWidth(int width) +{ + m_element["style"].set("width", std::to_string(width) + "px"); +} + +QRectF TitleBar::geometry() const +{ + return QRectF::fromDOMRect(m_element.call("getBoundingClientRect")); +} + +bool TitleBar::onPointerDown(const PointerEvent &event) +{ + if (event.pointerType != PointerType::Mouse) + return false; + + m_element.call("setPointerCapture", event.pointerId); + m_capturedPointerId = event.pointerId; + + const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint); + m_lastMovePoint = targetPointClippedToScreen; + m_window->onNonClientEvent(event); + return true; +} + +bool TitleBar::onPointerMove(const PointerEvent &event) +{ + if (m_capturedPointerId != event.pointerId) + return false; + + const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint); + const QPoint delta = targetPointClippedToScreen - m_lastMovePoint; + m_lastMovePoint = targetPointClippedToScreen; + + m_window->window()->setPosition(m_window->window()->position() + delta); + m_window->onNonClientEvent(event); + return true; +} + +bool TitleBar::onPointerUp(const PointerEvent &event) +{ + if (m_capturedPointerId != event.pointerId) + return false; + + m_element.call("releasePointerCapture", event.pointerId); + m_capturedPointerId = -1; + m_window->onNonClientEvent(event); + return true; +} + +bool TitleBar::onDoubleClick() +{ + m_window->onToggleMaximized(); + return true; +} + +QPoint TitleBar::clipPointWithScreen(const QPoint &pointInTitleBarCoords) const +{ + auto *screen = m_window->platformScreen(); + return screen->clipPoint(screen->mapFromLocal( + dom::mapPoint(m_element, screen->element(), pointInTitleBarCoords))); +} + +NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement) +{ + m_titleBar = std::make_unique(window, qtWindowElement); + m_resizer = std::make_unique(window, qtWindowElement); +} + +NonClientArea::~NonClientArea() = default; + +void NonClientArea::onClientAreaWidthChange(int width) +{ + m_titleBar->setWidth(width); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.h b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h new file mode 100644 index 0000000000..b561e8de78 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h @@ -0,0 +1,212 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QWASMWINDOWNONCLIENTAREA_H +#define QWASMWINDOWNONCLIENTAREA_H + +#include +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qstdweb { +class EventCallback; +} + +struct PointerEvent; +class QWindow; +class Resizer; +class TitleBar; +class QWasmWindow; + +class NonClientArea +{ +public: + NonClientArea(QWasmWindow *window, emscripten::val containerElement); + ~NonClientArea(); + + void onClientAreaWidthChange(int width); + TitleBar *titleBar() const { return m_titleBar.get(); } + +private: + std::unique_ptr m_resizer; + std::unique_ptr m_titleBar; +}; + +class WebImageButton +{ +public: + class Callbacks + { + public: + Callbacks(); + Callbacks(std::function onInteraction, std::function onClick); + ~Callbacks(); + + Callbacks(const Callbacks &) = delete; + Callbacks(Callbacks &&); + Callbacks &operator=(const Callbacks &) = delete; + Callbacks &operator=(Callbacks &&); + + operator bool() const { return !!m_onInteraction; } + + void onInteraction(); + void onClick(); + + private: + std::function m_onInteraction; + std::function m_onClick; + }; + + WebImageButton(); + ~WebImageButton(); + + void setCallbacks(Callbacks callbacks); + void setImage(std::string_view imageData, std::string_view format); + void setVisible(bool visible); + + emscripten::val htmlElement() const { return m_containerElement; } + emscripten::val imageElement() const { return m_imgElement; } + +private: + emscripten::val m_containerElement; + emscripten::val m_imgElement; + + std::unique_ptr m_webMouseMoveEventCallback; + std::unique_ptr m_webMouseDownEventCallback; + std::unique_ptr m_webClickEventCallback; + + Callbacks m_callbacks; +}; + +class Resizer +{ +public: + class ResizerElement + { + public: + static constexpr const char *cssClassNameForEdges(Qt::Edges edges) + { + switch (edges) { + case Qt::TopEdge | Qt::LeftEdge:; + return "nw"; + case Qt::TopEdge: + return "n"; + case Qt::TopEdge | Qt::RightEdge: + return "ne"; + case Qt::LeftEdge: + return "w"; + case Qt::RightEdge: + return "e"; + case Qt::BottomEdge | Qt::LeftEdge: + return "sw"; + case Qt::BottomEdge: + return "s"; + case Qt::BottomEdge | Qt::RightEdge: + return "se"; + default: + return ""; + } + } + + ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer); + ~ResizerElement(); + ResizerElement(const ResizerElement &other) = delete; + ResizerElement(ResizerElement &&other); + ResizerElement &operator=(const ResizerElement &other) = delete; + ResizerElement &operator=(ResizerElement &&other) = delete; + + bool onPointerDown(const PointerEvent &event); + bool onPointerMove(const PointerEvent &event); + bool onPointerUp(const PointerEvent &event); + + private: + emscripten::val m_element; + + int m_capturedPointerId = -1; + + const Qt::Edges m_edges; + + Resizer *m_resizer; + + std::unique_ptr m_mouseDownEvent; + std::unique_ptr m_mouseDragEvent; + std::unique_ptr m_mouseUpEvent; + }; + + using ClickCallback = std::function; + + Resizer(QWasmWindow *window, emscripten::val parentElement); + ~Resizer(); + +private: + void onInteraction(); + void startResize(Qt::Edges resizeEdges, const PointerEvent &event); + void continueResize(const PointerEvent &event); + void finishResize(); + + struct ResizeData + { + Qt::Edges edges = Qt::Edges::fromInt(0); + QPoint originInScreenCoords; + QPoint minShrink; + QPoint maxGrow; + QRect initialBounds; + }; + std::unique_ptr m_currentResizeData; + + QWasmWindow *m_window; + emscripten::val m_windowElement; + std::vector> m_elements; +}; + +class TitleBar +{ +public: + TitleBar(QWasmWindow *window, emscripten::val parentElement); + ~TitleBar(); + + void setTitle(const QString &title); + void setRestoreVisible(bool visible); + void setMaximizeVisible(bool visible); + void setIcon(std::string_view imageData, std::string_view format); + void setWidth(int width); + + QRectF geometry() const; + +private: + bool onPointerDown(const PointerEvent &event); + bool onPointerMove(const PointerEvent &event); + bool onPointerUp(const PointerEvent &event); + bool onDoubleClick(); + + QPoint clipPointWithScreen(const QPoint &pointInTitleBarCoords) const; + + QWasmWindow *m_window; + + emscripten::val m_element; + emscripten::val m_label; + + std::unique_ptr m_close; + std::unique_ptr m_maximize; + std::unique_ptr m_restore; + std::unique_ptr m_icon; + + int m_capturedPointerId = -1; + QPoint m_lastMovePoint; + + std::unique_ptr m_mouseDownEvent; + std::unique_ptr m_mouseDragEvent; + std::unique_ptr m_mouseUpEvent; + std::unique_ptr m_doubleClickEvent; +}; + +QT_END_NAMESPACE +#endif // QWASMWINDOWNONCLIENTAREA_H -- cgit v1.2.3