Handle the drop event in the wasm window element
Drop events are now handled in the wasm window element, which allows the browser to select the drop target automatically. This also fixes the case where drop data transfer finishes reading when a window has already been closed and destroyed - the cancellation flag is now owned by window so it gets invalidated as soon as window is gone. The code has also been structured with a new DragEvent passthrough. Fixes: QTBUG-109581 Change-Id: Ie3eb7446e2181fd540517f39397e8b35f111d009 Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io>
This commit is contained in:
parent
c290e742c6
commit
9b64bf0874
@ -37,7 +37,6 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
|
||||
qwasmwindowclientarea.cpp qwasmwindowclientarea.h
|
||||
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
|
||||
qwasminputcontext.cpp qwasminputcontext.h
|
||||
qwasmdrag.cpp qwasmdrag.h
|
||||
qwasmwindowstack.cpp qwasmwindowstack.h
|
||||
DEFINES
|
||||
QT_EGL_NO_X11
|
||||
|
@ -83,9 +83,6 @@ void QWasmCompositor::deregisterEventHandlers()
|
||||
emscripten_set_touchend_callback(screenElementSelector.constData(), 0, 0, NULL);
|
||||
emscripten_set_touchmove_callback(screenElementSelector.constData(), 0, 0, NULL);
|
||||
emscripten_set_touchcancel_callback(screenElementSelector.constData(), 0, 0, NULL);
|
||||
|
||||
screen()->element().call<void>("removeEventListener", std::string("drop"),
|
||||
val::module_property("qtDrop"), val(true));
|
||||
}
|
||||
|
||||
void QWasmCompositor::destroy()
|
||||
@ -123,11 +120,6 @@ void QWasmCompositor::initEventHandlers()
|
||||
&touchCallback);
|
||||
emscripten_set_touchcancel_callback(screenElementSelector.constData(), (void *)this, UseCapture,
|
||||
&touchCallback);
|
||||
|
||||
screen()->element().call<void>("addEventListener", std::string("drop"),
|
||||
val::module_property("qtDrop"), val(true));
|
||||
screen()->element().set("data-qtdropcontext", // ? unique
|
||||
emscripten::val(quintptr(reinterpret_cast<void *>(screen()))));
|
||||
}
|
||||
|
||||
void QWasmCompositor::addWindow(QWasmWindow *window)
|
||||
|
@ -1,78 +0,0 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "qwasmdrag.h"
|
||||
|
||||
#include "qwasmdom.h"
|
||||
#include "qwasmeventtranslator.h"
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
#include <QMimeData>
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <emscripten/bind.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace {
|
||||
Qt::DropAction parseDropActions(emscripten::val event)
|
||||
{
|
||||
const std::string dEffect = event["dataTransfer"]["dropEffect"].as<std::string>();
|
||||
|
||||
if (dEffect == "copy")
|
||||
return Qt::CopyAction;
|
||||
else if (dEffect == "move")
|
||||
return Qt::MoveAction;
|
||||
else if (dEffect == "link")
|
||||
return Qt::LinkAction;
|
||||
return Qt::IgnoreAction;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void dropEvent(emscripten::val event)
|
||||
{
|
||||
// someone dropped a file into the browser window
|
||||
// event is dataTransfer object
|
||||
// if drop event from outside browser, we do not get any mouse release, maybe mouse move
|
||||
// after the drop event
|
||||
event.call<void>("preventDefault"); // prevent browser from handling drop event
|
||||
|
||||
static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
|
||||
readDataCancellation = qstdweb::readDataTransfer(
|
||||
event["dataTransfer"],
|
||||
[](QByteArray fileContent) {
|
||||
QImage image;
|
||||
image.loadFromData(fileContent, nullptr);
|
||||
return image;
|
||||
},
|
||||
[wasmScreen = reinterpret_cast<QWasmScreen *>(
|
||||
event["target"]["data-qtdropcontext"].as<quintptr>()),
|
||||
event](std::unique_ptr<QMimeData> data) {
|
||||
const auto mouseDropPoint = QPoint(event["x"].as<int>(), event["y"].as<int>());
|
||||
const auto button = MouseEvent::buttonFromWeb(event["button"].as<int>());
|
||||
const Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(event);
|
||||
const auto dropAction = parseDropActions(event);
|
||||
auto *window = wasmScreen->topLevelAt(mouseDropPoint);
|
||||
|
||||
QWindowSystemInterface::handleDrag(window, data.get(), mouseDropPoint, dropAction,
|
||||
button, modifiers);
|
||||
|
||||
// drag drop
|
||||
QWindowSystemInterface::handleDrop(window, data.get(), mouseDropPoint, dropAction,
|
||||
button, modifiers);
|
||||
|
||||
// drag leave
|
||||
QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, {},
|
||||
{});
|
||||
});
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(drop_module)
|
||||
{
|
||||
function("qtDrop", &dropEvent);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
@ -1,17 +0,0 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef QWASMDRAG_H
|
||||
#define QWASMDRAG_H
|
||||
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
|
||||
#include <emscripten/val.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
void dropEvent(emscripten::val event);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QWASMDRAG_H
|
@ -16,10 +16,63 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
|
||||
}
|
||||
} // namespace KeyboardModifier
|
||||
|
||||
Event::Event(EventType type, emscripten::val target) : type(type), target(target) { }
|
||||
|
||||
Event::~Event() = default;
|
||||
|
||||
Event::Event(const Event &other) = default;
|
||||
|
||||
Event::Event(Event &&other) = default;
|
||||
|
||||
Event &Event::operator=(const Event &other) = default;
|
||||
|
||||
Event &Event::operator=(Event &&other) = default;
|
||||
|
||||
MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event["target"])
|
||||
{
|
||||
mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
|
||||
mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
|
||||
// The current button state (event.buttons) may be out of sync for some PointerDown
|
||||
// events where the "down" state is very brief, for example taps on Apple trackpads.
|
||||
// Qt expects that the current button state is in sync with the event, so we sync
|
||||
// it up here.
|
||||
if (type == EventType::PointerDown)
|
||||
mouseButtons |= mouseButton;
|
||||
localPoint = QPoint(event["offsetX"].as<int>(), event["offsetY"].as<int>());
|
||||
pointInPage = QPoint(event["pageX"].as<int>(), event["pageY"].as<int>());
|
||||
pointInViewport = QPoint(event["clientX"].as<int>(), event["clientY"].as<int>());
|
||||
modifiers = KeyboardModifier::getForEvent(event);
|
||||
}
|
||||
|
||||
MouseEvent::~MouseEvent() = default;
|
||||
|
||||
MouseEvent::MouseEvent(const MouseEvent &other) = default;
|
||||
|
||||
MouseEvent::MouseEvent(MouseEvent &&other) = default;
|
||||
|
||||
MouseEvent &MouseEvent::operator=(const MouseEvent &other) = default;
|
||||
|
||||
MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default;
|
||||
|
||||
PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
|
||||
{
|
||||
pointerId = event["pointerId"].as<int>();
|
||||
pointerType = event["pointerType"].as<std::string>() == "mouse" ? PointerType::Mouse
|
||||
: PointerType::Other;
|
||||
}
|
||||
|
||||
PointerEvent::~PointerEvent() = default;
|
||||
|
||||
PointerEvent::PointerEvent(const PointerEvent &other) = default;
|
||||
|
||||
PointerEvent::PointerEvent(PointerEvent &&other) = default;
|
||||
|
||||
PointerEvent &PointerEvent::operator=(const PointerEvent &other) = default;
|
||||
|
||||
PointerEvent &PointerEvent::operator=(PointerEvent &&other) = default;
|
||||
|
||||
std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
|
||||
{
|
||||
PointerEvent ret;
|
||||
|
||||
const auto eventType = ([&event]() -> std::optional<EventType> {
|
||||
const auto eventTypeString = event["type"].as<std::string>();
|
||||
|
||||
@ -38,27 +91,47 @@ std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
|
||||
if (!eventType)
|
||||
return std::nullopt;
|
||||
|
||||
ret.type = *eventType;
|
||||
ret.target = event["target"];
|
||||
ret.pointerType = event["pointerType"].as<std::string>() == "mouse" ?
|
||||
PointerType::Mouse : PointerType::Other;
|
||||
ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
|
||||
ret.mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
|
||||
return PointerEvent(*eventType, event);
|
||||
}
|
||||
|
||||
// The current button state (event.buttons) may be out of sync for some PointerDown
|
||||
// events where the "down" state is very brief, for example taps on Apple trackpads.
|
||||
// Qt expects that the current button state is in sync with the event, so we sync
|
||||
// it up here.
|
||||
if (*eventType == EventType::PointerDown)
|
||||
ret.mouseButtons |= ret.mouseButton;
|
||||
DragEvent::DragEvent(EventType type, emscripten::val event)
|
||||
: MouseEvent(type, event), dataTransfer(event["dataTransfer"])
|
||||
{
|
||||
dropAction = ([event]() {
|
||||
const std::string effect = event["dataTransfer"]["dropEffect"].as<std::string>();
|
||||
|
||||
ret.localPoint = QPoint(event["offsetX"].as<int>(), event["offsetY"].as<int>());
|
||||
ret.pointInPage = QPoint(event["pageX"].as<int>(), event["pageY"].as<int>());
|
||||
ret.pointInViewport = QPoint(event["clientX"].as<int>(), event["clientY"].as<int>());
|
||||
ret.pointerId = event["pointerId"].as<int>();
|
||||
ret.modifiers = KeyboardModifier::getForEvent(event);
|
||||
if (effect == "copy")
|
||||
return Qt::CopyAction;
|
||||
else if (effect == "move")
|
||||
return Qt::MoveAction;
|
||||
else if (effect == "link")
|
||||
return Qt::LinkAction;
|
||||
return Qt::IgnoreAction;
|
||||
})();
|
||||
}
|
||||
|
||||
return ret;
|
||||
DragEvent::~DragEvent() = default;
|
||||
|
||||
DragEvent::DragEvent(const DragEvent &other) = default;
|
||||
|
||||
DragEvent::DragEvent(DragEvent &&other) = default;
|
||||
|
||||
DragEvent &DragEvent::operator=(const DragEvent &other) = default;
|
||||
|
||||
DragEvent &DragEvent::operator=(DragEvent &&other) = default;
|
||||
|
||||
std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event)
|
||||
{
|
||||
const auto eventType = ([&event]() -> std::optional<EventType> {
|
||||
const auto eventTypeString = event["type"].as<std::string>();
|
||||
|
||||
if (eventTypeString == "drop")
|
||||
return EventType::Drop;
|
||||
return std::nullopt;
|
||||
})();
|
||||
if (!eventType)
|
||||
return std::nullopt;
|
||||
return DragEvent(*eventType, event);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -18,6 +18,7 @@
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
enum class EventType {
|
||||
Drop,
|
||||
PointerDown,
|
||||
PointerMove,
|
||||
PointerUp,
|
||||
@ -107,13 +108,20 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
|
||||
|
||||
} // namespace KeyboardModifier
|
||||
|
||||
struct Q_CORE_EXPORT Event
|
||||
struct Event
|
||||
{
|
||||
EventType type;
|
||||
emscripten::val target = emscripten::val::undefined();
|
||||
|
||||
Event(EventType type, emscripten::val target);
|
||||
~Event();
|
||||
Event(const Event &other);
|
||||
Event(Event &&other);
|
||||
Event &operator=(const Event &other);
|
||||
Event &operator=(Event &&other);
|
||||
};
|
||||
|
||||
struct Q_CORE_EXPORT MouseEvent : public Event
|
||||
struct MouseEvent : public Event
|
||||
{
|
||||
QPoint localPoint;
|
||||
QPoint pointInPage;
|
||||
@ -122,6 +130,13 @@ struct Q_CORE_EXPORT MouseEvent : public Event
|
||||
Qt::MouseButtons mouseButtons;
|
||||
QFlags<Qt::KeyboardModifier> modifiers;
|
||||
|
||||
MouseEvent(EventType type, emscripten::val webEvent);
|
||||
~MouseEvent();
|
||||
MouseEvent(const MouseEvent &other);
|
||||
MouseEvent(MouseEvent &&other);
|
||||
MouseEvent &operator=(const MouseEvent &other);
|
||||
MouseEvent &operator=(MouseEvent &&other);
|
||||
|
||||
static constexpr Qt::MouseButton buttonFromWeb(int webButton) {
|
||||
switch (webButton) {
|
||||
case 0:
|
||||
@ -158,14 +173,36 @@ struct Q_CORE_EXPORT MouseEvent : public Event
|
||||
}
|
||||
};
|
||||
|
||||
struct Q_CORE_EXPORT PointerEvent : public MouseEvent
|
||||
struct PointerEvent : public MouseEvent
|
||||
{
|
||||
static std::optional<PointerEvent> fromWeb(emscripten::val webEvent);
|
||||
|
||||
PointerEvent(EventType type, emscripten::val webEvent);
|
||||
~PointerEvent();
|
||||
PointerEvent(const PointerEvent &other);
|
||||
PointerEvent(PointerEvent &&other);
|
||||
PointerEvent &operator=(const PointerEvent &other);
|
||||
PointerEvent &operator=(PointerEvent &&other);
|
||||
|
||||
PointerType pointerType;
|
||||
int pointerId;
|
||||
};
|
||||
|
||||
struct DragEvent : public MouseEvent
|
||||
{
|
||||
static std::optional<DragEvent> fromWeb(emscripten::val webEvent);
|
||||
|
||||
DragEvent(EventType type, emscripten::val webEvent);
|
||||
~DragEvent();
|
||||
DragEvent(const DragEvent &other);
|
||||
DragEvent(DragEvent &&other);
|
||||
DragEvent &operator=(const DragEvent &other);
|
||||
DragEvent &operator=(DragEvent &&other);
|
||||
|
||||
Qt::DropAction dropAction;
|
||||
emscripten::val dataTransfer;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QWASMEVENT_H
|
||||
|
@ -80,12 +80,6 @@ QWasmIntegration::QWasmIntegration()
|
||||
m_clipboard(new QWasmClipboard),
|
||||
m_accessibility(new QWasmAccessibility)
|
||||
{
|
||||
// Temporary measure to make dropEvent appear in the library. EMSCRIPTEN_KEEPALIVE does not
|
||||
// work, nor does a Q_CONSTRUCTOR_FUNCTION in qwasmdrag.cpp.
|
||||
volatile bool foolEmcc = false;
|
||||
if (foolEmcc)
|
||||
dropEvent(emscripten::val::undefined());
|
||||
|
||||
s_instance = this;
|
||||
|
||||
touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
|
||||
|
@ -12,10 +12,6 @@
|
||||
#include <qpa/qplatformscreen.h>
|
||||
#include <qpa/qplatforminputcontext.h>
|
||||
|
||||
#if QT_CONFIG(draganddrop)
|
||||
# include "qwasmdrag.h"
|
||||
#endif
|
||||
|
||||
#include <QtCore/qhash.h>
|
||||
|
||||
#include <private/qsimpledrag_p.h>
|
||||
|
@ -84,6 +84,12 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
|
||||
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", callback);
|
||||
m_pointerLeaveCallback =
|
||||
std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", callback);
|
||||
|
||||
m_dropCallback = std::make_unique<qstdweb::EventCallback>(
|
||||
m_qtWindow, "drop", [this](emscripten::val event) {
|
||||
if (processDrop(*DragEvent::fromWeb(event)))
|
||||
event.call<void>("preventDefault");
|
||||
});
|
||||
}
|
||||
|
||||
QWasmWindow::~QWasmWindow()
|
||||
@ -418,6 +424,30 @@ bool QWasmWindow::processPointer(const PointerEvent &event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QWasmWindow::processDrop(const DragEvent &event)
|
||||
{
|
||||
m_dropDataReadCancellationFlag = qstdweb::readDataTransfer(
|
||||
event.dataTransfer,
|
||||
[](QByteArray fileContent) {
|
||||
QImage image;
|
||||
image.loadFromData(fileContent, nullptr);
|
||||
return image;
|
||||
},
|
||||
[this, event](std::unique_ptr<QMimeData> data) {
|
||||
QWindowSystemInterface::handleDrag(window(), data.get(), event.pointInPage,
|
||||
event.dropAction, event.mouseButton,
|
||||
event.modifiers);
|
||||
|
||||
QWindowSystemInterface::handleDrop(window(), data.get(), event.pointInPage,
|
||||
event.dropAction, event.mouseButton,
|
||||
event.modifiers);
|
||||
|
||||
QWindowSystemInterface::handleDrag(window(), nullptr, QPoint(), Qt::IgnoreAction,
|
||||
{}, {});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
QRect QWasmWindow::normalGeometry() const
|
||||
{
|
||||
return m_normalGeometry;
|
||||
|
@ -18,13 +18,21 @@
|
||||
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace qstdweb {
|
||||
struct CancellationFlag;
|
||||
}
|
||||
|
||||
namespace qstdweb {
|
||||
class EventCallback;
|
||||
}
|
||||
|
||||
class ClientArea;
|
||||
struct DragEvent;
|
||||
struct PointerEvent;
|
||||
|
||||
class QWasmWindow final : public QPlatformWindow
|
||||
{
|
||||
@ -84,6 +92,7 @@ private:
|
||||
void applyWindowState();
|
||||
|
||||
bool processPointer(const PointerEvent &event);
|
||||
bool processDrop(const DragEvent &event);
|
||||
|
||||
QWindow *m_window = nullptr;
|
||||
QWasmCompositor *m_compositor = nullptr;
|
||||
@ -105,6 +114,8 @@ private:
|
||||
std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
|
||||
|
||||
Qt::WindowStates m_state = Qt::WindowNoState;
|
||||
Qt::WindowStates m_previousWindowState = Qt::WindowNoState;
|
||||
|
||||
@ -120,6 +131,8 @@ private:
|
||||
friend class QWasmCompositor;
|
||||
friend class QWasmEventTranslator;
|
||||
bool windowIsPopupType(Qt::WindowFlags flags) const;
|
||||
|
||||
std::shared_ptr<qstdweb::CancellationFlag> m_dropDataReadCancellationFlag;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
Loading…
x
Reference in New Issue
Block a user