diff options
author | Lorn Potter <lorn.potter@gmail.com> | 2023-10-11 09:45:55 +1000 |
---|---|---|
committer | Lorn Potter <lorn.potter@gmail.com> | 2023-12-21 17:24:42 +1000 |
commit | e62fd1b7062af421cd289ff542514bd4332e1933 (patch) | |
tree | d8cea005075310fae8da6b5987aa06ed102482d5 /src/plugins | |
parent | 5af9c3d0e7e244009ffb4195ebcffb32101d8f04 (diff) |
wasm: add QWasmDrag back to handle drag/drop
Change-Id: I7c577e6b13db9f5c51e5691baaf6417b956a5ff4
Done-with: Mikolaj.Boc@qt.io
Pick-to: 6.7
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/platforms/wasm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmdrag.cpp | 280 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmdrag.h | 47 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmevent.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmintegration.cpp | 4 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmintegration.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.cpp | 28 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindow.h | 9 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowclientarea.cpp | 29 | ||||
-rw-r--r-- | src/plugins/platforms/wasm/qwasmwindowclientarea.h | 5 |
10 files changed, 374 insertions, 35 deletions
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 7cb073a552..42ee9b1eb5 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -36,6 +36,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h qwasminputcontext.cpp qwasminputcontext.h qwasmwindowstack.cpp qwasmwindowstack.h + qwasmdrag.cpp qwasmdrag.h DEFINES QT_EGL_NO_X11 QT_NO_FOREACH diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp new file mode 100644 index 0000000000..82f225158c --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmdrag.cpp @@ -0,0 +1,280 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwasmdrag.h" + +#include "qwasmbase64iconstore.h" +#include "qwasmdom.h" +#include "qwasmevent.h" +#include "qwasmintegration.h" + +#include <qpa/qwindowsysteminterface.h> + +#include <QtCore/private/qstdweb_p.h> +#include <QtCore/qeventloop.h> +#include <QtCore/qmimedata.h> +#include <QtCore/qtimer.h> + +#include <functional> +#include <string> +#include <utility> + +QT_BEGIN_NAMESPACE + +namespace { + +QWindow *windowForDrag(QDrag *drag) +{ + QWindow *window = qobject_cast<QWindow *>(drag->source()); + if (window) + return window; + if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1) + return nullptr; + + QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle", + Q_RETURN_ARG(QWindow *, window)); + return window; +} + +} // namespace + +struct QWasmDrag::DragState +{ + class DragImage + { + public: + DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window); + ~DragImage(); + + emscripten::val htmlElement(); + + private: + emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData); + emscripten::val generateDragImageFromText(const QMimeData *mimeData); + emscripten::val generateDefaultDragImage(); + emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap); + + emscripten::val m_imageDomElement; + emscripten::val m_temporaryImageElementParent; + }; + + DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure); + ~DragState(); + DragState(const QWasmDrag &other) = delete; + DragState(QWasmDrag &&other) = delete; + DragState &operator=(const QWasmDrag &other) = delete; + DragState &operator=(QWasmDrag &&other) = delete; + + QDrag *drag; + QWindow *window; + std::function<void()> quitEventLoopClosure; + std::unique_ptr<DragImage> dragImage; + Qt::DropAction dropAction = Qt::DropAction::IgnoreAction; +}; + +QWasmDrag::QWasmDrag() = default; + +QWasmDrag::~QWasmDrag() = default; + +QWasmDrag *QWasmDrag::instance() +{ + return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag()); +} + +Qt::DropAction QWasmDrag::drag(QDrag *drag) +{ + Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress"); + + QWindow *window = windowForDrag(drag); + if (!window) + return Qt::IgnoreAction; + + Qt::DropAction dragResult = Qt::IgnoreAction; + if (qstdweb::haveJspi()) { + QEventLoop loop; + m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); }); + loop.exec(); + dragResult = m_dragState->dropAction; + m_dragState.reset(); + } + + if (dragResult == Qt::IgnoreAction) + dragResult = QBasicDrag::drag(drag); + + return dragResult; +} + +void QWasmDrag::onNativeDragStarted(DragEvent *event) +{ + Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO, + "The event is not a DragStart event"); + // It is possible for a drag start event to arrive from another window. + if (!m_dragState || m_dragState->window != event->targetWindow) { + event->cancelDragStart(); + return; + } + + m_dragState->dragImage = std::make_unique<DragState::DragImage>( + m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow); + event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(), + m_dragState->drag->hotSpot()); + event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData()); +} + +void QWasmDrag::onNativeDragOver(DragEvent *event) +{ + auto mimeDataPreview = event->dataTransfer.toMimeDataPreview(); + + const Qt::DropActions actions = m_dragState + ? m_dragState->drag->supportedActions() + : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction + | Qt::DropAction::LinkAction); + + const auto dragResponse = QWindowSystemInterface::handleDrag( + event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions, + event->mouseButton, event->modifiers); + event->acceptDragOver(); + if (dragResponse.isAccepted()) { + event->dataTransfer.setDropAction(dragResponse.acceptedAction()); + } else { + event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction); + } +} + +void QWasmDrag::onNativeDrop(DragEvent *event) +{ + QWasmWindow *wasmWindow = QWasmWindow::fromWindow(event->targetWindow); + + const auto localScreenElementPoint = dom::mapPoint( + event->target(), wasmWindow->platformScreen()->element(), event->localPoint); + const auto pointInQtScreen = + wasmWindow->platformScreen()->mapFromLocal(localScreenElementPoint); + const QPointF pointInTargetWindowCoords = event->targetWindow->mapFromGlobal(pointInQtScreen); + + const Qt::DropActions actions = m_dragState + ? m_dragState->drag->supportedActions() + : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction + | Qt::DropAction::LinkAction); + + auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction); + QMimeData *data = event->dataTransfer.toMimeDataWithFile(); + *dropResponse = QWindowSystemInterface::handleDrop(event->targetWindow, data, + pointInTargetWindowCoords.toPoint(), actions, + event->mouseButton, event->modifiers); + + if (dropResponse->isAccepted()) { + event->acceptDrop(); + event->dataTransfer.setDropAction(dropResponse->acceptedAction()); + + m_dragState->dropAction = dropResponse->acceptedAction(); + } +} + +void QWasmDrag::onNativeDragFinished(DragEvent *event) +{ + m_dragState->dropAction = event->dropAction; + m_dragState->quitEventLoopClosure(); +} + +QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData, + QWindow *window) + : m_temporaryImageElementParent(QWasmWindow::fromWindow(window)->containerElement()) +{ + m_imageDomElement = generateDragImage(pixmap, mimeData); + + m_imageDomElement.set("className", "hidden-drag-image"); + m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement); +} + +QWasmDrag::DragState::DragImage::~DragImage() +{ + m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement); +} + +emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap, + const QMimeData *mimeData) +{ + if (!pixmap.isNull()) + return generateDragImageFromPixmap(pixmap); + if (mimeData->hasFormat("text/plain")) + return generateDragImageFromText(mimeData); + return generateDefaultDragImage(); +} + +emscripten::val +QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData) +{ + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("span")); + + constexpr qsizetype MaxCharactersInDragImage = 100; + + const auto text = QString::fromUtf8(mimeData->data("text/plain")); + dragImageElement.set( + "innerText", + text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString()); + return dragImageElement; +} + +emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage() +{ + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("div")); + + auto innerImgElement = emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("img")); + innerImgElement.set("src", + "data:image/" + std::string("svg+xml") + ";base64," + + std::string(Base64IconStore::get()->getIcon( + Base64IconStore::IconType::QtLogo))); + + constexpr char DragImageSize[] = "50px"; + + dragImageElement["style"].set("width", DragImageSize); + innerImgElement["style"].set("width", DragImageSize); + dragImageElement["style"].set("display", "flex"); + + dragImageElement.call<void>("appendChild", innerImgElement); + return dragImageElement; +} + +emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap) +{ + emscripten::val dragImageElement = + emscripten::val::global("document") + .call<emscripten::val>("createElement", emscripten::val("canvas")); + dragImageElement.set("width", pixmap.width()); + dragImageElement.set("height", pixmap.height()); + + dragImageElement["style"].set( + "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px"); + dragImageElement["style"].set( + "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px"); + + auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d")); + auto imageData = context2d.call<emscripten::val>( + "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height())); + + dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888), + imageData, QRect(0, 0, pixmap.width(), pixmap.height())); + context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0)); + + return dragImageElement; +} + +emscripten::val QWasmDrag::DragState::DragImage::htmlElement() +{ + return m_imageDomElement; +} + +QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window, + std::function<void()> quitEventLoopClosure) + : drag(drag), window(window), quitEventLoopClosure(std::move(quitEventLoopClosure)) +{ +} + +QWasmDrag::DragState::~DragState() = default; + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h new file mode 100644 index 0000000000..4a591a0f28 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmdrag.h @@ -0,0 +1,47 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWINDOWSDRAG_H +#define QWINDOWSDRAG_H + +#include <private/qstdweb_p.h> +#include <private/qsimpledrag_p.h> + +#include <qpa/qplatformdrag.h> +#include <QtGui/qdrag.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +struct DragEvent; + +class QWasmDrag final : public QSimpleDrag +{ +public: + QWasmDrag(); + ~QWasmDrag() override; + QWasmDrag(const QWasmDrag &other) = delete; + QWasmDrag(QWasmDrag &&other) = delete; + QWasmDrag &operator=(const QWasmDrag &other) = delete; + QWasmDrag &operator=(QWasmDrag &&other) = delete; + + static QWasmDrag *instance(); + + void onNativeDragOver(DragEvent *event); + void onNativeDrop(DragEvent *event); + void onNativeDragStarted(DragEvent *event); + void onNativeDragFinished(DragEvent *event); + + // QPlatformDrag: + Qt::DropAction drag(QDrag *drag) final; + +private: + struct DragState; + + std::unique_ptr<DragState> m_dragState; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSDRAG_H diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h index c5c20a0b5e..610f61dd66 100644 --- a/src/plugins/platforms/wasm/qwasmevent.h +++ b/src/plugins/platforms/wasm/qwasmevent.h @@ -10,7 +10,7 @@ #include <QtCore/qglobal.h> #include <QtCore/qnamespace.h> #include <QtGui/qevent.h> - +#include <private/qstdweb_p.h> #include <QPoint> #include <emscripten/html5.h> diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp index 2fbb33eb2d..1e83779036 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.cpp +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -14,6 +14,8 @@ #include "qwasmwindow.h" #include "qwasmbackingstore.h" #include "qwasmfontdatabase.h" +#include "qwasmdrag.h" + #include <qpa/qplatformwindow.h> #include <QtGui/qscreen.h> #include <qpa/qwindowsysteminterface.h> @@ -138,7 +140,7 @@ QWasmIntegration::QWasmIntegration() visualViewport.call<void>("addEventListener", val("resize"), val::module_property("qtResizeAllScreens")); } - m_drag = std::make_unique<QSimpleDrag>(); + m_drag = std::make_unique<QWasmDrag>(); } QWasmIntegration::~QWasmIntegration() diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h index b966487760..eeb68109a0 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.h +++ b/src/plugins/platforms/wasm/qwasmintegration.h @@ -14,7 +14,6 @@ #include <QtCore/qhash.h> -#include <private/qsimpledrag_p.h> #include <private/qstdweb_p.h> #include <emscripten.h> @@ -33,6 +32,7 @@ class QWasmBackingStore; class QWasmClipboard; class QWasmAccessibility; class QWasmServices; +class QWasmDrag; class QWasmIntegration : public QObject, public QPlatformIntegration { @@ -102,7 +102,7 @@ private: mutable QWasmInputContext *m_platformInputContext = nullptr; #if QT_CONFIG(draganddrop) - std::unique_ptr<QSimpleDrag> m_drag; + std::unique_ptr<QWasmDrag> m_drag; #endif }; diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index fa7eed1464..7a1ab87683 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -114,11 +114,6 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback); m_pointerLeaveCallback = std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback); - m_dropCallback = std::make_unique<qstdweb::EventCallback>( - m_qtWindow, "drop", [this](emscripten::val event) { - if (processDrop(*DragEvent::fromWeb(event, window()))) - event.call<void>("preventDefault"); - }); m_wheelEventCallback = std::make_unique<qstdweb::EventCallback>( m_qtWindow, "wheel", [this](emscripten::val event) { @@ -157,6 +152,11 @@ QSurfaceFormat QWasmWindow::format() const return window()->requestedFormat(); } +QWasmWindow *QWasmWindow::fromWindow(QWindow *window) +{ + return static_cast<QWasmWindow *>(window->handle()); +} + void QWasmWindow::onRestoreClicked() { window()->setWindowState(Qt::WindowNoState); @@ -535,24 +535,6 @@ bool QWasmWindow::processPointer(const PointerEvent &event) return false; } -bool QWasmWindow::processDrop(const DragEvent &event) -{ - dom::DataTransfer transfer(event.dataTransfer.webDataTransfer["clipboardData"]); - QMimeData *data = transfer.toMimeDataWithFile(); - // TODO handle file - QWindowSystemInterface::handleDrag(window(), data, - event.pointInPage.toPoint(), event.dropAction, - event.mouseButton, event.modifiers); - - QWindowSystemInterface::handleDrop(window(), data, - event.pointInPage.toPoint(), event.dropAction, - event.mouseButton, event.modifiers); - - QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction, - {}, {}); - return true; -} - bool QWasmWindow::processWheel(const WheelEvent &event) { // Web scroll deltas are inverted from Qt deltas - negate. diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index 3e7f215b6f..2c4c0d3df3 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -26,15 +26,10 @@ QT_BEGIN_NAMESPACE namespace qstdweb { -struct CancellationFlag; -} - -namespace qstdweb { class EventCallback; } class ClientArea; -struct DragEvent; struct KeyEvent; struct PointerEvent; class QWasmDeadKeySupport; @@ -49,6 +44,7 @@ public: QWasmBackingStore *backingStore); ~QWasmWindow() final; + static QWasmWindow *fromWindow(QWindow *window); QSurfaceFormat format() const override; void paint(); @@ -127,7 +123,6 @@ private: bool processKey(const KeyEvent &event); bool processPointer(const PointerEvent &event); - bool processDrop(const DragEvent &event); bool processWheel(const WheelEvent &event); QWindow *m_window = nullptr; @@ -174,8 +169,6 @@ private: friend class QWasmCompositor; friend class QWasmEventTranslator; bool windowIsPopupType(Qt::WindowFlags flags) const; - - std::shared_ptr<qstdweb::CancellationFlag> m_dropDataReadCancellationFlag; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp index 2c9f215760..047b5f432c 100644 --- a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp +++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp @@ -7,6 +7,7 @@ #include "qwasmevent.h" #include "qwasmscreen.h" #include "qwasmwindow.h" +#include "qwasmdrag.h" #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpointingdevice.h> @@ -31,6 +32,34 @@ ClientArea::ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(element, "pointerup", callback); m_pointerCancelCallback = std::make_unique<qstdweb::EventCallback>(element, "pointercancel", callback); + + element.call<void>("setAttribute", emscripten::val("draggable"), emscripten::val("true")); + + m_dragStartCallback = std::make_unique<qstdweb::EventCallback>( + element, "dragstart", [this](emscripten::val webEvent) { + webEvent.call<void>("preventDefault"); + auto event = *DragEvent::fromWeb(webEvent, m_window->window()); + QWasmDrag::instance()->onNativeDragStarted(&event); + }); + m_dragOverCallback = std::make_unique<qstdweb::EventCallback>( + element, "dragover", [this](emscripten::val webEvent) { + webEvent.call<void>("preventDefault"); + auto event = *DragEvent::fromWeb(webEvent, m_window->window()); + QWasmDrag::instance()->onNativeDragOver(&event); + }); + m_dropCallback = std::make_unique<qstdweb::EventCallback>( + element, "drop", [this](emscripten::val webEvent) { + webEvent.call<void>("preventDefault"); + auto event = *DragEvent::fromWeb(webEvent, m_window->window()); + QWasmDrag::instance()->onNativeDrop(&event); + }); + m_dragEndCallback = std::make_unique<qstdweb::EventCallback>( + element, "dragend", [this](emscripten::val webEvent) { + webEvent.call<void>("preventDefault"); + auto event = *DragEvent::fromWeb(webEvent, m_window->window()); + QWasmDrag::instance()->onNativeDragFinished(&event); + }); + } bool ClientArea::processPointer(const PointerEvent &event) diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.h b/src/plugins/platforms/wasm/qwasmwindowclientarea.h index e8b082f11e..ba745a59a8 100644 --- a/src/plugins/platforms/wasm/qwasmwindowclientarea.h +++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.h @@ -36,6 +36,11 @@ private: std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback; std::unique_ptr<qstdweb::EventCallback> m_pointerCancelCallback; + std::unique_ptr<qstdweb::EventCallback> m_dragOverCallback; + std::unique_ptr<qstdweb::EventCallback> m_dragStartCallback; + std::unique_ptr<qstdweb::EventCallback> m_dragEndCallback; + std::unique_ptr<qstdweb::EventCallback> m_dropCallback; + QMap<int, QWindowSystemInterface::TouchPoint> m_pointerIdToTouchPoints; QWasmScreen *m_screen; |