wasm: add QWasmDrag back to handle drag/drop

Change-Id: I7c577e6b13db9f5c51e5691baaf6417b956a5ff4
Done-with: Mikolaj.Boc@qt.io
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
(cherry picked from commit e62fd1b7062af421cd289ff542514bd4332e1933)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Lorn Potter 2023-10-11 09:45:55 +10:00 committed by Qt Cherry-pick Bot
parent c257e2bf99
commit e8fa50346c
10 changed files with 374 additions and 35 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>
@ -132,7 +134,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()

View File

@ -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
{
@ -101,7 +101,7 @@ private:
mutable QWasmInputContext *m_platformInputContext = nullptr;
#if QT_CONFIG(draganddrop)
std::unique_ptr<QSimpleDrag> m_drag;
std::unique_ptr<QWasmDrag> m_drag;
#endif
};

View File

@ -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.

View File

@ -25,16 +25,11 @@
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

View File

@ -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)

View File

@ -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;