Improve window dragging on WASM

Window dragging has been considerably improved by replacing the mouse
events by pointer events and placing a pointer lock on WASM canvas, so
that off-browser window events are delivered to us.

Translation of the drag origin has been limited to inside the canvas, so
that a window cannot be dragged so far that it becomes offscreen and is
unreachable.

Change-Id: Id177c630a6466f04464a513371d6b97d3a098b6a
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Mikolaj Boc 2022-07-15 09:11:11 +02:00
parent 57b4e30ff8
commit 5a4e5c62af
13 changed files with 508 additions and 245 deletions

View File

@ -15,12 +15,14 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
qwasmclipboard.cpp qwasmclipboard.h
qwasmcompositor.cpp qwasmcompositor.h
qwasmcursor.cpp qwasmcursor.h
qwasmevent.cpp qwasmevent.h
qwasmeventdispatcher.cpp qwasmeventdispatcher.h
qwasmeventtranslator.cpp qwasmeventtranslator.h
qwasmfontdatabase.cpp qwasmfontdatabase.h
qwasmintegration.cpp qwasmintegration.h
qwasmoffscreensurface.cpp qwasmoffscreensurface.h
qwasmopenglcontext.cpp qwasmopenglcontext.h
qwasmplatform.cpp qwasmplatform.h
qwasmscreen.cpp qwasmscreen.h
qwasmservices.cpp qwasmservices.h
qwasmstring.cpp qwasmstring.h

View File

@ -7,6 +7,7 @@
#include "qwasmeventtranslator.h"
#include "qwasmeventdispatcher.h"
#include "qwasmclipboard.h"
#include "qwasmevent.h"
#include <QtOpenGL/qopengltexture.h>
@ -58,6 +59,7 @@ EMSCRIPTEN_BINDINGS(qtMouseModule) {
QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
: QObject(screen)
, m_windowManipulation(screen)
, m_blitter(new QOpenGLTextureBlitter)
, m_eventTranslator(std::make_unique<QWasmEventTranslator>())
{
@ -87,12 +89,6 @@ void QWasmCompositor::deregisterEventHandlers()
emscripten_set_keydown_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_keyup_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_mousedown_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_mouseup_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_mousemove_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_mouseenter_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_mouseleave_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_focus_callback(canvasSelector.constData(), 0, 0, NULL);
emscripten_set_wheel_callback(canvasSelector.constData(), 0, 0, NULL);
@ -133,9 +129,7 @@ void QWasmCompositor::initEventHandlers()
{
QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
m_eventTranslator->g_usePlatformMacSpecifics
= (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform);
if (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform) {
if (platform() == Platform::MacOS) {
if (!emscripten::val::global("window")["safari"].isUndefined()) {
val canvas = screen()->canvas();
canvas.call<void>("addEventListener",
@ -149,11 +143,17 @@ void QWasmCompositor::initEventHandlers()
emscripten_set_keydown_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb);
emscripten_set_keyup_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb);
emscripten_set_mousedown_callback(canvasSelector.constData(), (void *)this, UseCapture, &mouse_cb);
emscripten_set_mouseup_callback(canvasSelector.constData(), (void *)this, UseCapture, &mouse_cb);
emscripten_set_mousemove_callback(canvasSelector.constData(), (void *)this, UseCapture, &mouse_cb);
emscripten_set_mouseenter_callback(canvasSelector.constData(), (void *)this, UseCapture, &mouse_cb);
emscripten_set_mouseleave_callback(canvasSelector.constData(), (void *)this, UseCapture, &mouse_cb);
val canvas = screen()->canvas();
const auto callback = std::function([this](emscripten::val event) {
if (processPointer(*PointerEvent::fromWeb(event)))
event.call<void>("preventDefault");
});
m_pointerDownCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerdown", callback);
m_pointerMoveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointermove", callback);
m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerup", callback);
m_pointerEnterCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerenter", callback);
m_pointerLeaveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerleave", callback);
emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, UseCapture, &focus_cb);
@ -164,7 +164,6 @@ void QWasmCompositor::initEventHandlers()
emscripten_set_touchmove_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
emscripten_set_touchcancel_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
val canvas = screen()->canvas();
canvas.call<void>("addEventListener",
std::string("drop"),
val::module_property("qtDrop"), val(true));
@ -859,60 +858,27 @@ void QWasmCompositor::frame()
m_context->swapBuffers(someWindow->window());
}
void QWasmCompositor::resizeWindow(QWindow *window, QWasmCompositor::ResizeMode mode,
QRect startRect, QPoint amount)
void QWasmCompositor::WindowManipulation::resizeWindow(const QPoint& amount)
{
if (mode == QWasmCompositor::ResizeNone)
return;
const auto& minShrink = std::get<ResizeState>(m_state->operationSpecific).m_minShrink;
const auto& maxGrow = std::get<ResizeState>(m_state->operationSpecific).m_maxGrow;
const auto& resizeMode = std::get<ResizeState>(m_state->operationSpecific).m_resizeMode;
bool top = mode == QWasmCompositor::ResizeTopLeft ||
mode == QWasmCompositor::ResizeTop ||
mode == QWasmCompositor::ResizeTopRight;
const QPoint cappedGrowVector(
std::min(maxGrow.x(), std::max(minShrink.x(),
(resizeMode & Left) ? -amount.x() : (resizeMode & Right) ? amount.x() : 0)),
std::min(maxGrow.y(), std::max(minShrink.y(),
(resizeMode & Top) ? -amount.y() : (resizeMode & Bottom) ? amount.y() : 0)));
bool bottom = mode == QWasmCompositor::ResizeBottomLeft ||
mode == QWasmCompositor::ResizeBottom ||
mode == QWasmCompositor::ResizeBottomRight;
bool left = mode == QWasmCompositor::ResizeLeft ||
mode == QWasmCompositor::ResizeTopLeft ||
mode == QWasmCompositor::ResizeBottomLeft;
bool right = mode == QWasmCompositor::ResizeRight ||
mode == QWasmCompositor::ResizeTopRight ||
mode == QWasmCompositor::ResizeBottomRight;
int x1 = startRect.left();
int y1 = startRect.top();
int x2 = startRect.right();
int y2 = startRect.bottom();
if (left)
x1 += amount.x();
if (top)
y1 += amount.y();
if (right)
x2 += amount.x();
if (bottom)
y2 += amount.y();
int w = x2-x1;
int h = y2-y1;
if (w < window->minimumWidth()) {
if (left)
x1 -= window->minimumWidth() - w;
w = window->minimumWidth();
}
if (h < window->minimumHeight()) {
if (top)
y1 -= window->minimumHeight() - h;
h = window->minimumHeight();
}
window->setGeometry(x1, y1, w, h);
const auto& initialBounds =
std::get<ResizeState>(m_state->operationSpecific).m_initialWindowBounds;
m_state->window->setGeometry(
initialBounds.adjusted(
(resizeMode & Left) ? -cappedGrowVector.x() : 0,
(resizeMode & Top) ? -cappedGrowVector.y() : 0,
(resizeMode & Right) ? cappedGrowVector.x() : 0,
(resizeMode & Bottom) ? cappedGrowVector.y() : 0
));
}
void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window)
@ -945,12 +911,6 @@ int QWasmCompositor::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *k
return static_cast<int>(wasmCompositor->processKeyboard(eventType, keyEvent));
}
int QWasmCompositor::mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
{
QWasmCompositor *compositor = (QWasmCompositor*)userData;
return static_cast<int>(compositor->processMouse(eventType, mouseEvent));
}
int QWasmCompositor::focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData)
{
Q_UNUSED(eventType)
@ -972,19 +932,18 @@ int QWasmCompositor::touchCallback(int eventType, const EmscriptenTouchEvent *to
return static_cast<int>(compositor->handleTouch(eventType, touchEvent));
}
bool QWasmCompositor::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent)
bool QWasmCompositor::processPointer(const PointerEvent& event)
{
const Qt::MouseButton button = QWasmEventTranslator::translateMouseButton(mouseEvent->button);
if (event.pointerType != PointerType::Mouse)
return false;
const QPoint targetPointInCanvasCoords(mouseEvent->targetX, mouseEvent->targetY);
const QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords;
const QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + event.point;
QEvent::Type buttonEventType = QEvent::None;
Qt::KeyboardModifiers modifiers = m_eventTranslator->translateMouseEventModifier(mouseEvent);
QWindow *const targetWindow = ([this, &targetPointInScreenCoords]() -> QWindow * {
auto *targetWindow =
m_resizeMode == QWasmCompositor::ResizeNone ?
m_windowManipulation.operation() == WindowManipulation::Operation::None ?
screen()->compositor()->windowAt(targetPointInScreenCoords, 5) : nullptr;
return targetWindow ? targetWindow : m_lastMouseTargetWindow.get();
@ -1006,57 +965,41 @@ bool QWasmCompositor::processMouse(int eventType, const EmscriptenMouseEvent *mo
Qt::WindowStates windowState = targetWindow->windowState();
const bool isTargetWindowResizable = !windowState.testFlag(Qt::WindowMaximized) && !windowState.testFlag(Qt::WindowFullScreen);
switch (eventType) {
case EMSCRIPTEN_EVENT_MOUSEDOWN:
switch (event.type) {
case EventType::PointerDown:
{
buttonEventType = QEvent::MouseButtonPress;
m_pressedButtons.setFlag(button);
if (targetWindow)
targetWindow->requestActivate();
m_pressedWindow = targetWindow;
if (isTargetWindowResizable && button == Qt::MouseButton::LeftButton && !isTargetWindowBlocked) {
if (wasmTargetWindow->isPointOnTitle(targetPointInScreenCoords)) {
m_windowBeingManipulated = targetWindow;
} else if (wasmTargetWindow->isPointOnResizeRegion(targetPointInScreenCoords)) {
m_windowBeingManipulated = targetWindow;
m_resizeMode = wasmTargetWindow->resizeModeAtPoint(targetPointInScreenCoords);
m_resizePoint = targetPointInScreenCoords;
m_resizeStartRect = targetWindow->geometry();
}
}
m_windowManipulation.onPointerDown(event, targetWindow);
wasmTargetWindow->injectMousePressed(pointInTargetWindowCoords, targetPointInScreenCoords, button, modifiers);
wasmTargetWindow->injectMousePressed(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
break;
}
case EMSCRIPTEN_EVENT_MOUSEUP:
case EventType::PointerUp:
{
buttonEventType = QEvent::MouseButtonRelease;
m_pressedButtons.setFlag(button, false);
if (m_windowBeingManipulated && m_pressedButtons.testFlag(Qt::NoButton)) {
m_windowBeingManipulated = nullptr;
m_resizeMode = QWasmCompositor::ResizeNone;
}
m_windowManipulation.onPointerUp(event);
if (m_pressedWindow) {
// Always deliver the released event to the same window that was pressed
AsWasmWindow(m_pressedWindow)->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, button, modifiers);
if (button == Qt::MouseButton::LeftButton)
AsWasmWindow(m_pressedWindow)->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
if (event.mouseButton == Qt::MouseButton::LeftButton)
m_pressedWindow = nullptr;
} else {
wasmTargetWindow->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, button, modifiers);
wasmTargetWindow->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
}
break;
}
case EMSCRIPTEN_EVENT_MOUSEMOVE:
case EventType::PointerMove:
{
buttonEventType = QEvent::MouseMove;
if (wasmTargetWindow && m_pressedButtons.testFlag(Qt::NoButton)) {
if (wasmTargetWindow && event.mouseButtons.testFlag(Qt::NoButton)) {
const bool isOnResizeRegion = wasmTargetWindow->isPointOnResizeRegion(targetPointInScreenCoords);
if (isTargetWindowResizable && isOnResizeRegion && !isTargetWindowBlocked) {
@ -1073,34 +1016,26 @@ bool QWasmCompositor::processMouse(int eventType, const EmscriptenMouseEvent *mo
}
}
if (m_windowBeingManipulated) {
if (m_resizeMode == QWasmCompositor::ResizeNone) {
m_windowBeingManipulated->setPosition(
m_windowBeingManipulated->position() + QPoint(mouseEvent->movementX, mouseEvent->movementY));
} else {
const QPoint delta = targetPointInCanvasCoords - m_resizePoint;
resizeWindow(m_windowBeingManipulated, m_resizeMode, m_resizeStartRect, delta);
}
}
m_windowManipulation.onPointerMove(event);
break;
}
case EMSCRIPTEN_EVENT_MOUSEENTER:
processMouseEnter(mouseEvent);
case EventType::PointerEnter:
processMouseEnter(nullptr);
break;
case EMSCRIPTEN_EVENT_MOUSELEAVE:
case EventType::PointerLeave:
processMouseLeave();
break;
default:
break;
};
if (!pointerIsWithinTargetWindowBounds && m_pressedButtons.testFlag(Qt::NoButton)) {
if (!pointerIsWithinTargetWindowBounds && event.mouseButtons.testFlag(Qt::NoButton)) {
leaveWindow(m_lastMouseTargetWindow);
}
bool shouldDeliverEvent = pointerIsWithinTargetWindowBounds;
QWindow *eventTarget = targetWindow;
if (!eventTarget && buttonEventType == QEvent::MouseButtonRelease) {
if (!eventTarget && event.type == EventType::PointerUp) {
eventTarget = m_lastMouseTargetWindow;
m_lastMouseTargetWindow = nullptr;
shouldDeliverEvent = true;
@ -1109,13 +1044,122 @@ bool QWasmCompositor::processMouse(int eventType, const EmscriptenMouseEvent *mo
eventTarget != nullptr && shouldDeliverEvent &&
QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>(
eventTarget, QWasmIntegration::getTimestamp(), pointInTargetWindowCoords, targetPointInScreenCoords,
m_pressedButtons, button, buttonEventType, modifiers);
event.mouseButtons, event.mouseButton, buttonEventType, event.modifiers);
if (!eventAccepted && buttonEventType == QEvent::MouseButtonPress)
if (!eventAccepted && event.type == EventType::PointerDown)
QGuiApplicationPrivate::instance()->closeAllPopups();
return eventAccepted;
}
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 std::holds_alternative<MoveState>(m_state->operationSpecific)
? Operation::Move : Operation::Resize;
}
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;
const auto pointInScreenCoords = m_screen->geometry().topLeft() + event.point;
std::unique_ptr<std::variant<ResizeState, MoveState>> operationSpecific;
if (AsWasmWindow(windowAtPoint)->isPointOnTitle(pointInScreenCoords)) {
operationSpecific = std::make_unique<std::variant<ResizeState, MoveState>>(MoveState {
.m_lastPointInScreenCoords = pointInScreenCoords
});
} else if (AsWasmWindow(windowAtPoint)->isPointOnResizeRegion(pointInScreenCoords)) {
operationSpecific = std::make_unique<std::variant<ResizeState, MoveState>>(ResizeState {
.m_resizeMode = AsWasmWindow(windowAtPoint)->resizeModeAtPoint(pointInScreenCoords),
.m_originInScreenCoords = pointInScreenCoords,
.m_initialWindowBounds = windowAtPoint->geometry(),
.m_minShrink = QPoint(windowAtPoint->minimumWidth() - windowAtPoint->geometry().width(),
windowAtPoint->minimumHeight() - windowAtPoint->geometry().height()),
.m_maxGrow = QPoint(
windowAtPoint->maximumWidth() - windowAtPoint->geometry().width(),
windowAtPoint->maximumHeight() - windowAtPoint->geometry().height()),
});
} else {
return;
}
m_state.reset(new OperationState{
.pointerId = event.pointerId,
.window = windowAtPoint,
.operationSpecific = std::move(*operationSpecific),
});
m_screen->canvas().call<void>("setPointerCapture", event.pointerId);
}
void QWasmCompositor::WindowManipulation::onPointerMove(
const PointerEvent& event)
{
if (operation() == Operation::None || event.pointerId != m_state->pointerId)
return;
const auto pointInScreenCoords = m_screen->geometry().topLeft() + event.point;
switch (operation()) {
case Operation::Move: {
const QPoint targetPointClippedToScreen(
std::max(m_screen->geometry().left(), std::min(m_screen->geometry().right(), pointInScreenCoords.x())),
std::max(m_screen->geometry().top(), std::min(m_screen->geometry().bottom(), pointInScreenCoords.y())));
const QPoint difference = targetPointClippedToScreen -
std::get<MoveState>(m_state->operationSpecific).m_lastPointInScreenCoords;
std::get<MoveState>(m_state->operationSpecific).m_lastPointInScreenCoords = targetPointClippedToScreen;
m_state->window->setPosition(m_state->window->position() + difference);
break;
}
case Operation::Resize: {
resizeWindow(pointInScreenCoords -
std::get<ResizeState>(m_state->operationSpecific).m_originInScreenCoords);
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();
m_screen->canvas().call<void>("releasePointerCapture", event.pointerId);
}
bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent)
{
Qt::Key qtKey;
@ -1139,7 +1183,7 @@ bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEve
if (keyType == QEvent::None)
return 0;
QFlags<Qt::KeyboardModifier> modifiers = m_eventTranslator->translateKeyboardEventModifier(keyEvent);
QFlags<Qt::KeyboardModifier> modifiers = KeyboardModifier::getForEvent(*keyEvent);
// Clipboard fallback path: cut/copy/paste are handled by clipboard event
// handlers if direct clipboard access is not available.
@ -1180,7 +1224,7 @@ bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wh
{
Q_UNUSED(eventType);
EmscriptenMouseEvent mouseEvent = wheelEvent->mouse;
const EmscriptenMouseEvent* mouseEvent = &wheelEvent->mouse;
int scrollFactor = 0;
switch (wheelEvent->deltaMode) {
@ -1197,8 +1241,8 @@ bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wh
scrollFactor = -scrollFactor; // Web scroll deltas are inverted from Qt deltas.
Qt::KeyboardModifiers modifiers = m_eventTranslator->translateMouseEventModifier(&mouseEvent);
QPoint targetPointInCanvasCoords(mouseEvent.targetX, mouseEvent.targetY);
Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(*mouseEvent);
QPoint targetPointInCanvasCoords(mouseEvent->targetX, mouseEvent->targetY);
QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords;
QWindow *targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5);
@ -1287,7 +1331,7 @@ int QWasmCompositor::handleTouch(int eventType, const EmscriptenTouchEvent *touc
touchPointList.append(touchPoint);
}
QFlags<Qt::KeyboardModifier> keyModifier = m_eventTranslator->translateTouchEventModifier(touchEvent);
QFlags<Qt::KeyboardModifier> keyModifier = KeyboardModifier::getForEvent(*touchEvent);
bool accepted = false;

View File

@ -11,6 +11,7 @@
#include <QtGui/qpalette.h>
#include <QtGui/qpainter.h>
#include <QtGui/qinputdevice.h>
#include <QtCore/private/qstdweb_p.h>
#include <QPointer>
#include <QPointingDevice>
@ -21,6 +22,7 @@
QT_BEGIN_NAMESPACE
struct PointerEvent;
class QWasmWindow;
class QWasmScreen;
class QOpenGLContext;
@ -69,16 +71,23 @@ public:
};
Q_DECLARE_FLAGS(StateFlags, QWasmStateFlag)
enum ResizeDimension {
Left = 1,
Right = 1 << 1,
Top = 1 << 2,
Bottom = 1 << 3
};
enum ResizeMode {
ResizeNone,
ResizeTopLeft,
ResizeTop,
ResizeTopRight,
ResizeRight,
ResizeBottomRight,
ResizeBottom,
ResizeBottomLeft,
ResizeLeft
ResizeTopLeft = Top | Left,
ResizeTop = Top,
ResizeTopRight = Top | Right,
ResizeRight = Right,
ResizeBottomRight = Bottom | Right,
ResizeBottom = Bottom,
ResizeBottomLeft = Bottom | Left,
ResizeLeft = Left
};
struct QWasmTitleBarOptions {
@ -125,11 +134,9 @@ public:
void deliverUpdateRequests();
void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
void handleBackingStoreFlush();
bool processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent);
bool processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent);
bool processWheel(int eventType, const EmscriptenWheelEvent *wheelEvent);
int handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent);
void resizeWindow(QWindow *window, QWasmCompositor::ResizeMode mode, QRect startRect, QPoint amount);
bool processMouseEnter(const EmscriptenMouseEvent *mouseEvent);
bool processMouseLeave();
@ -140,6 +147,47 @@ private slots:
void frame();
private:
class WindowManipulation {
public:
enum class Operation {
None,
Move,
Resize,
};
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 ResizeState {
ResizeMode m_resizeMode;
QPoint m_originInScreenCoords;
QRect m_initialWindowBounds;
const QPoint m_minShrink;
const QPoint m_maxGrow;
};
struct MoveState {
QPoint m_lastPointInScreenCoords;
};
struct OperationState
{
int pointerId;
QPointer<QWindow> window;
std::variant<ResizeState, MoveState> operationSpecific;
};
void resizeWindow(const QPoint& amount);
QWasmScreen *m_screen;
std::unique_ptr<OperationState> m_state;
};
void notifyTopWindowChanged(QWasmWindow *window);
void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
@ -156,12 +204,15 @@ private:
int alignment, const QPixmap &pixmap) const;
static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
static int mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
static int focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData);
static int wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData);
bool processPointer(const PointerEvent& event);
static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData);
WindowManipulation m_windowManipulation;
QScopedPointer<QOpenGLContext> m_context;
QScopedPointer<QOpenGLTextureBlitter> m_blitter;
@ -170,7 +221,6 @@ private:
QRegion m_globalDamage; // damage caused by expose, window close, etc.
bool m_needComposit = false;
bool m_inFlush = false;
bool m_inResize = false;
bool m_isEnabled = true;
QSize m_targetSize;
qreal m_targetDevicePixelRatio = 1;
@ -179,14 +229,15 @@ private:
int m_requestAnimationFrameId = -1;
bool m_inDeliverUpdateRequest = false;
QPointer<QWindow> m_windowBeingManipulated;
QPointer<QWindow> m_pressedWindow;
QPointer<QWindow> m_lastMouseTargetWindow;
Qt::MouseButtons m_pressedButtons = Qt::NoButton;
ResizeMode m_resizeMode = ResizeNone;
QPoint m_resizePoint;
QRect m_resizeStartRect;
std::unique_ptr<qstdweb::EventCallback> m_pointerDownCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
std::unique_ptr<QPointingDevice> m_touchDevice;
QMap <int, QPointF> m_pressedTouchIds;

View File

@ -64,8 +64,7 @@ static void dropEvent(val event)
if (wasmDrag->m_mimeData)
delete wasmDrag->m_mimeData;
wasmDrag->m_mimeData = new QMimeData;
int button = event["button"].as<int>();
wasmDrag->m_qButton = QWasmEventTranslator::translateMouseButton(button);
wasmDrag->m_qButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
wasmDrag->m_keyModifiers = Qt::NoModifier;
if (event["altKey"].as<bool>())

View File

@ -0,0 +1,53 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmevent.h"
QT_BEGIN_NAMESPACE
namespace KeyboardModifier
{
template <>
QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
const EmscriptenKeyboardEvent& event)
{
return internal::Helper<EmscriptenKeyboardEvent>::getModifierForEvent(event) |
(event.location == DOM_KEY_LOCATION_NUMPAD ? Qt::KeypadModifier : Qt::NoModifier);
}
} // namespace KeyboardModifier
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>();
if (eventTypeString == "pointermove")
return EventType::PointerMove;
else if (eventTypeString == "pointerup")
return EventType::PointerUp;
else if (eventTypeString == "pointerdown")
return EventType::PointerDown;
else if (eventTypeString == "pointerenter")
return EventType::PointerEnter;
else if (eventTypeString == "pointerleave")
return EventType::PointerLeave;
return std::nullopt;
})();
if (!eventType)
return std::nullopt;
ret.type = *eventType;
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>());
ret.point = QPoint(event["x"].as<int>(), event["y"].as<int>());
ret.pointerId = event["pointerId"].as<int>();
ret.modifiers = KeyboardModifier::getForEvent(event);
return ret;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,145 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMEVENT_H
#define QWASMEVENT_H
#include "qwasmplatform.h"
#include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h>
#include <QPoint>
#include <emscripten/html5.h>
#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
enum class EventType {
PointerDown,
PointerMove,
PointerUp,
PointerEnter,
PointerLeave,
};
enum class PointerType {
Mouse,
Other,
};
namespace KeyboardModifier {
namespace internal
{
// Check for the existence of shiftKey, ctrlKey, altKey and metaKey in a type.
// Based on that, we can safely assume we are dealing with an emscripten event type.
template<typename T>
struct IsEmscriptenEvent
{
template<typename U, EM_BOOL U::*, EM_BOOL U::*, EM_BOOL U::*, EM_BOOL U::*>
struct SFINAE {};
template<typename U> static char Test(
SFINAE<U, &U::shiftKey, &U::ctrlKey, &U::altKey, &U::metaKey>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
template<class T, typename Enable = void>
struct Helper;
template<class T>
struct Helper<T, std::enable_if_t<IsEmscriptenEvent<T>::value>>
{
static QFlags<Qt::KeyboardModifier> getModifierForEvent(const T& event) {
QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
if (event.shiftKey)
keyModifier |= Qt::ShiftModifier;
if (event.ctrlKey)
keyModifier |= platform() == Platform::MacOS ? Qt::MetaModifier : Qt::ControlModifier;
if (event.altKey)
keyModifier |= Qt::AltModifier;
if (event.metaKey)
keyModifier |= platform() == Platform::MacOS ? Qt::ControlModifier : Qt::MetaModifier;
return keyModifier;
}
};
template<>
struct Helper<emscripten::val>
{
static QFlags<Qt::KeyboardModifier> getModifierForEvent(const emscripten::val& event) {
QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
if (event["shiftKey"].as<bool>())
keyModifier |= Qt::ShiftModifier;
if (event["ctrlKey"].as<bool>())
keyModifier |= platform() == Platform::MacOS ? Qt::MetaModifier : Qt::ControlModifier;
if (event["altKey"].as<bool>())
keyModifier |= Qt::AltModifier;
if (event["metaKey"].as<bool>())
keyModifier |= platform() == Platform::MacOS ? Qt::ControlModifier : Qt::MetaModifier;
if (event["constructor"]["name"].as<std::string>() == "KeyboardEvent" &&
event["location"].as<unsigned int>() == DOM_KEY_LOCATION_NUMPAD) {
keyModifier |= Qt::KeypadModifier;
}
return keyModifier;
}
};
} // namespace internal
template <typename Event>
QFlags<Qt::KeyboardModifier> getForEvent(const Event& event)
{
return internal::Helper<Event>::getModifierForEvent(event);
}
template <>
QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
const EmscriptenKeyboardEvent& event);
} // namespace KeyboardModifier
struct Q_CORE_EXPORT Event
{
EventType type;
};
struct Q_CORE_EXPORT MouseEvent : public Event
{
QPoint point;
Qt::MouseButton mouseButton;
Qt::MouseButtons mouseButtons;
QFlags<Qt::KeyboardModifier> modifiers;
static constexpr Qt::MouseButton buttonFromWeb(int webButton) {
switch (webButton) {
case 0:
return Qt::LeftButton;
case 1:
return Qt::MiddleButton;
case 2:
return Qt::RightButton;
default:
return Qt::NoButton;
}
}
static constexpr Qt::MouseButtons buttonsFromWeb(unsigned short webButtons) {
// Coincidentally, Qt and web bitfields match.
return Qt::MouseButtons::fromInt(webButtons);
}
};
struct Q_CORE_EXPORT PointerEvent : public MouseEvent
{
static std::optional<PointerEvent> fromWeb(emscripten::val webEvent);
PointerType pointerType;
int pointerId;
};
QT_END_NAMESPACE
#endif // QWASMEVENT_H

View File

@ -160,54 +160,6 @@ QWasmEventTranslator::~QWasmEventTranslator()
{
}
template <typename Event>
QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translatKeyModifier(const Event *event)
{
// macOS CTRL <-> META switching. We most likely want to enable
// the existing switching code in QtGui, but for now do it here.
QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
if (event->shiftKey)
keyModifier |= Qt::ShiftModifier;
if (event->ctrlKey) {
if (g_usePlatformMacSpecifics)
keyModifier |= Qt::MetaModifier;
else
keyModifier |= Qt::ControlModifier;
}
if (event->altKey)
keyModifier |= Qt::AltModifier;
if (event->metaKey) {
if (g_usePlatformMacSpecifics)
keyModifier |= Qt::ControlModifier;
else
keyModifier |= Qt::MetaModifier;
}
return keyModifier;
}
QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateKeyboardEventModifier(const EmscriptenKeyboardEvent *event)
{
QFlags<Qt::KeyboardModifier> keyModifier = translatKeyModifier(event);
if (event->location == DOM_KEY_LOCATION_NUMPAD) {
keyModifier |= Qt::KeypadModifier;
}
return keyModifier;
}
QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent)
{
return translatKeyModifier(mouseEvent);
}
QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateTouchEventModifier(const EmscriptenTouchEvent *touchEvent)
{
return translatKeyModifier(touchEvent);
}
Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey)
{
Qt::Key qtKey = Qt::Key_unknown;

View File

@ -12,6 +12,8 @@
#include <QtGui/qinputdevice.h>
#include <QHash>
#include <QCursor>
#include "qwasmevent.h"
#include "qwasmplatform.h"
QT_BEGIN_NAMESPACE
@ -26,25 +28,7 @@ public:
explicit QWasmEventTranslator();
~QWasmEventTranslator();
template <typename Event>
QFlags<Qt::KeyboardModifier> translatKeyModifier(const Event *event);
static Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey);
QFlags<Qt::KeyboardModifier> translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent);
QFlags<Qt::KeyboardModifier> translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent);
QFlags<Qt::KeyboardModifier> translateTouchEventModifier(const EmscriptenTouchEvent *touchEvent);
static constexpr Qt::MouseButton translateMouseButton(unsigned short button) {
switch (button) {
case 0:
return Qt::LeftButton;
case 1:
return Qt::MiddleButton;
case 2:
return Qt::RightButton;
default:
return Qt::NoButton;
}
}
static QCursor cursorForMode(QWasmCompositor::ResizeMode mode);
QString getKeyText(const EmscriptenKeyboardEvent *keyEvent, Qt::Key key);

View File

@ -50,7 +50,7 @@ QWasmInputContext::QWasmInputContext()
m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
m_inputElement.set("contentaediable","true");
if (QWasmIntegration::get()->platform == QWasmIntegration::AndroidPlatform) {
if (platform() == Platform::Android) {
emscripten::val body = document["body"];
body.call<void>("appendChild", m_inputElement);
@ -65,8 +65,7 @@ QWasmInputContext::QWasmInputContext()
&androidKeyboardCallback);
}
if (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform ||
QWasmIntegration::get()->platform == QWasmIntegration::iPhonePlatform)
if (platform() == Platform::MacOS || platform() == Platform::iPhone)
{
auto callback = [=](emscripten::val) {
m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
@ -81,7 +80,7 @@ QWasmInputContext::QWasmInputContext()
QWasmInputContext::~QWasmInputContext()
{
if (QWasmIntegration::get()->platform == QWasmIntegration::AndroidPlatform)
if (platform() == Platform::Android)
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
}
@ -107,7 +106,7 @@ void QWasmInputContext::update(Qt::InputMethodQueries queries)
void QWasmInputContext::showInputPanel()
{
if (QWasmIntegration::get()->platform == QWasmIntegration::WindowsPlatform
if (platform() == Platform::Windows
&& inputPanelIsOpen) // call this only once for win32
return;
// this is called each time the keyboard is touched
@ -119,9 +118,9 @@ void QWasmInputContext::showInputPanel()
// captured by the keyboard event handler installed on the
// canvas.
if (QWasmIntegration::get()->platform == QWasmIntegration::MacOSPlatform // keep for compatibility
|| QWasmIntegration::get()->platform == QWasmIntegration::iPhonePlatform
|| QWasmIntegration::get()->platform == QWasmIntegration::WindowsPlatform) {
if (platform() == Platform::MacOS // keep for compatibility
|| platform() == Platform::iPhone
|| platform() == Platform::Windows) {
emscripten::val canvas = focusCanvas();
if (canvas == emscripten::val::undefined())
return;

View File

@ -86,22 +86,6 @@ QWasmIntegration::QWasmIntegration()
s_instance = this;
touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
// The Platform Detect: expand coverage as needed
platform = GenericPlatform;
emscripten::val rawPlatform = emscripten::val::global("navigator")["platform"];
if (rawPlatform.call<bool>("includes", emscripten::val("Mac")))
platform = MacOSPlatform;
if (rawPlatform.call<bool>("includes", emscripten::val("iPhone")))
platform = iPhonePlatform;
if (rawPlatform.call<bool>("includes", emscripten::val("Win32")))
platform = WindowsPlatform;
if (rawPlatform.call<bool>("includes", emscripten::val("Linux"))) {
platform = LinuxPlatform;
emscripten::val uAgent = emscripten::val::global("navigator")["userAgent"];
if (uAgent.call<bool>("includes", emscripten::val("Android")))
platform = AndroidPlatform;
}
// Create screens for container elements. Each container element can be a div element (preferred),
// or a canvas element (legacy). Qt versions prior to 6.x read the "qtCanvasElements" module property,

View File

@ -40,15 +40,6 @@ class QWasmIntegration : public QObject, public QPlatformIntegration
{
Q_OBJECT
public:
enum Platform {
GenericPlatform,
MacOSPlatform,
WindowsPlatform,
LinuxPlatform,
AndroidPlatform,
iPhonePlatform
};
QWasmIntegration();
~QWasmIntegration();
@ -89,7 +80,6 @@ public:
void removeBackingStore(QWindow* window);
static quint64 getTimestamp();
Platform platform;
int touchPoints;
private:

View File

@ -0,0 +1,31 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmplatform.h"
QT_BEGIN_NAMESPACE
Platform platform()
{
static const Platform qtDetectedPlatform = ([]() {
// The Platform Detect: expand coverage as needed
emscripten::val rawPlatform = emscripten::val::global("navigator")["platform"];
if (rawPlatform.call<bool>("includes", emscripten::val("Mac")))
return Platform::MacOS;
if (rawPlatform.call<bool>("includes", emscripten::val("iPhone")))
return Platform::iPhone;
if (rawPlatform.call<bool>("includes", emscripten::val("Win32")))
return Platform::Windows;
if (rawPlatform.call<bool>("includes", emscripten::val("Linux"))) {
emscripten::val uAgent = emscripten::val::global("navigator")["userAgent"];
if (uAgent.call<bool>("includes", emscripten::val("Android")))
return Platform::Android;
return Platform::Linux;
}
return Platform::Generic;
})();
return qtDetectedPlatform;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,29 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMPLATFORM_H
#define QWASMPLATFORM_H
#include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h>
#include <QPoint>
#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
enum class Platform {
Generic,
MacOS,
Windows,
Linux,
Android,
iPhone,
};
Platform platform();
QT_END_NAMESPACE
#endif // QWASMPLATFORM_H