diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmcompositor.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmcompositor.cpp | 747 |
1 files changed, 368 insertions, 379 deletions
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index 7787367e01..92340b25ba 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -7,6 +7,7 @@ #include "qwasmeventtranslator.h" #include "qwasmeventdispatcher.h" #include "qwasmclipboard.h" +#include "qwasmevent.h" #include <QtOpenGL/qopengltexture.h> @@ -25,18 +26,16 @@ #include <emscripten/bind.h> +namespace { +QWasmWindow *AsWasmWindow(QWindow *window) { + return static_cast<QWasmWindow*>(window->handle()); +} +} // namespace + using namespace emscripten; Q_GUI_EXPORT int qt_defaultDpiX(); -QWasmCompositedWindow::QWasmCompositedWindow() - : window(nullptr) - , parentWindow(nullptr) - , flushPending(false) - , visible(false) -{ -} - bool g_scrollingInvertedFromDevice = false; static void mouseWheelEvent(emscripten::val event) @@ -51,32 +50,23 @@ EMSCRIPTEN_BINDINGS(qtMouseModule) { } QWasmCompositor::QWasmCompositor(QWasmScreen *screen) - :QObject(screen) + : QObject(screen) + , m_windowManipulation(screen) , m_blitter(new QOpenGLTextureBlitter) - , m_needComposit(false) - , m_inFlush(false) - , m_inResize(false) - , m_isEnabled(true) - , m_targetDevicePixelRatio(1) - , draggedWindow(nullptr) - , lastWindow(nullptr) - , pressedButtons(Qt::NoButton) - , resizeMode(QWasmCompositor::ResizeNone) - , eventTranslator(new QWasmEventTranslator()) - , mouseInCanvas(false) + , m_eventTranslator(std::make_unique<QWasmEventTranslator>()) { - touchDevice = new QPointingDevice( + m_touchDevice = std::make_unique<QPointingDevice>( "touchscreen", 1, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, QPointingDevice::Capability::Position | QPointingDevice::Capability::Area | QPointingDevice::Capability::NormalizedPosition, 10, 0); - QWindowSystemInterface::registerInputDevice(touchDevice); + QWindowSystemInterface::registerInputDevice(m_touchDevice.get()); } QWasmCompositor::~QWasmCompositor() { - windowUnderMouse.clear(); + m_windowUnderMouse.clear(); if (m_requestAnimationFrameId != -1) emscripten_cancel_animation_frame(m_requestAnimationFrameId); @@ -91,12 +81,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); @@ -137,9 +121,7 @@ void QWasmCompositor::initEventHandlers() { QByteArray canvasSelector = screen()->canvasTargetId().toUtf8(); - 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", @@ -148,25 +130,32 @@ void QWasmCompositor::initEventHandlers() } } - emscripten_set_keydown_callback(canvasSelector.constData(), (void *)this, 1, &keyboard_cb); - emscripten_set_keyup_callback(canvasSelector.constData(), (void *)this, 1, &keyboard_cb); + constexpr EM_BOOL UseCapture = 1; + + 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, 1, &mouse_cb); - emscripten_set_mouseup_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb); - emscripten_set_mousemove_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb); - emscripten_set_mouseenter_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb); - emscripten_set_mouseleave_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb); + val canvas = screen()->canvas(); + const auto callback = std::function([this](emscripten::val event) { + if (processPointer(*PointerEvent::fromWeb(event))) + event.call<void>("preventDefault"); + }); - emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, 1, &focus_cb); + 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_wheel_callback(canvasSelector.constData(), (void *)this, 1, &wheel_cb); + emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, UseCapture, &focus_cb); - emscripten_set_touchstart_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback); - emscripten_set_touchend_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback); - emscripten_set_touchmove_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback); - emscripten_set_touchcancel_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback); + emscripten_set_wheel_callback(canvasSelector.constData(), (void *)this, UseCapture, &wheel_cb); + + emscripten_set_touchstart_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); + emscripten_set_touchend_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback); + 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)); @@ -179,64 +168,46 @@ void QWasmCompositor::setEnabled(bool enabled) m_isEnabled = enabled; } -void QWasmCompositor::addWindow(QWasmWindow *window, QWasmWindow *parentWindow) +void QWasmCompositor::addWindow(QWasmWindow *window) { - QWasmCompositedWindow compositedWindow; - compositedWindow.window = window; - compositedWindow.parentWindow = parentWindow; - m_compositedWindows.insert(window, compositedWindow); + m_windowVisibility.insert(window, false); - if (parentWindow == 0) - m_windowStack.append(window); - else - m_compositedWindows[parentWindow].childWindows.append(window); + m_windowStack.append(window); notifyTopWindowChanged(window); } void QWasmCompositor::removeWindow(QWasmWindow *window) { - QWasmWindow *platformWindow = m_compositedWindows[window].parentWindow; - - if (platformWindow) { - QWasmWindow *parentWindow = window; - m_compositedWindows[parentWindow].childWindows.removeAll(window); - } - m_windowStack.removeAll(window); - m_compositedWindows.remove(window); + m_windowVisibility.remove(window); m_requestUpdateWindows.remove(window); if (!m_windowStack.isEmpty() && !QGuiApplication::focusWindow()) { - auto lastWindow = m_windowStack.last(); - lastWindow->requestActivateWindow(); - notifyTopWindowChanged(lastWindow); + auto m_lastMouseTargetWindow = m_windowStack.last(); + m_lastMouseTargetWindow->requestActivateWindow(); + notifyTopWindowChanged(m_lastMouseTargetWindow); } } void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) { - QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; - if (compositedWindow.visible == visible) + const bool wasVisible = m_windowVisibility[window]; + if (wasVisible == visible) return; - compositedWindow.visible = visible; - compositedWindow.flushPending = true; - if (visible) - compositedWindow.damage = compositedWindow.window->geometry(); - else - m_globalDamage = compositedWindow.window->geometry(); // repaint previously covered area. + m_windowVisibility[window] = visible; + if (!visible) + m_globalDamage = window->window()->geometry(); // repaint previously covered area. requestUpdateWindow(window, QWasmCompositor::ExposeEventDelivery); } void QWasmCompositor::raise(QWasmWindow *window) { - if (m_compositedWindows.size() <= 1) + if (m_windowStack.size() <= 1) return; - QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; - compositedWindow.damage = compositedWindow.window->geometry(); m_windowStack.removeAll(window); m_windowStack.append(window); @@ -245,47 +216,31 @@ void QWasmCompositor::raise(QWasmWindow *window) void QWasmCompositor::lower(QWasmWindow *window) { - if (m_compositedWindows.size() <= 1) + if (m_windowStack.size() <= 1) return; m_windowStack.removeAll(window); m_windowStack.prepend(window); - QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; - m_globalDamage = compositedWindow.window->geometry(); // repaint previously covered area. + m_globalDamage = window->window()->geometry(); // repaint previously covered area. notifyTopWindowChanged(window); } -void QWasmCompositor::setParent(QWasmWindow *window, QWasmWindow *parent) -{ - m_compositedWindows[window].parentWindow = parent; - - requestUpdate(); -} - int QWasmCompositor::windowCount() const { return m_windowStack.count(); } -QWindow *QWasmCompositor::windowAt(QPoint globalPoint, int padding) const +QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding) const { - int index = m_windowStack.count() - 1; - // qDebug() << "window at" << "point" << p << "window count" << index; - - while (index >= 0) { - const QWasmCompositedWindow &compositedWindow = m_compositedWindows[m_windowStack.at(index)]; - //qDebug() << "windwAt testing" << compositedWindow.window << - - QRect geometry = compositedWindow.window->windowFrameGeometry() - .adjusted(-padding, -padding, padding, padding); - - if (compositedWindow.visible && geometry.contains(globalPoint)) - return m_windowStack.at(index)->window(); - --index; - } - - return 0; + const auto found = std::find_if(m_windowStack.rbegin(), m_windowStack.rend(), + [this, padding, &targetPointInScreenCoords](const QWasmWindow* window) { + const QRect geometry = window->windowFrameGeometry() + .adjusted(-padding, -padding, padding, padding); + + return m_windowVisibility[window] && geometry.contains(targetPointInScreenCoords); + }); + return found != m_windowStack.rend() ? (*found)->window() : nullptr; } QWindow *QWasmCompositor::keyWindow() const @@ -549,7 +504,7 @@ void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWas int height = window->windowFrameGeometry().height(); qreal dpr = window->devicePixelRatio(); - QImage image(QSize(width * dpr, height * dpr), QImage::Format_RGB32); + QImage image(QSize(width * dpr, height * dpr), QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(dpr); QPainter painter(&image); painter.fillRect(QRect(0, 0, width, height), painter.background()); @@ -570,12 +525,19 @@ void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWas texture.setMinificationFilter(QOpenGLTexture::Nearest); texture.setMagnificationFilter(QOpenGLTexture::Nearest); texture.setWrapMode(QOpenGLTexture::ClampToEdge); - texture.setData(image, QOpenGLTexture::DontGenerateMipMaps); + texture.setFormat(QOpenGLTexture::RGBAFormat); + texture.setSize(image.width(), image.height()); + texture.setMipLevels(1); + texture.allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + + QOpenGLPixelTransferOptions uploadOptions; + uploadOptions.setAlignment(1); + texture.create(); texture.bind(); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, - image.constScanLine(0)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, + GL_UNSIGNED_BYTE, image.constScanLine(0)); QRect windowCanvasGeometry = window->windowFrameGeometry().translated(-screen->geometry().topLeft()); blit(blitter, screen, &texture, windowCanvasGeometry); @@ -812,7 +774,7 @@ void QWasmCompositor::frame() QWasmWindow *someWindow = nullptr; - for (QWasmWindow *window : qAsConst(m_windowStack)) { + for (QWasmWindow *window : std::as_const(m_windowStack)) { if (window->window()->surfaceClass() == QSurface::Window && qt_window_private(static_cast<QWindow *>(window->window()))->receivedExpose) { someWindow = window; @@ -846,13 +808,9 @@ void QWasmCompositor::frame() m_blitter->bind(); m_blitter->setRedBlueSwizzle(true); - for (QWasmWindow *window : qAsConst(m_windowStack)) { - QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; - - if (!compositedWindow.visible) - continue; - - drawWindow(m_blitter.data(), screen(), window); + for (QWasmWindow *window : std::as_const(m_windowStack)) { + if (m_windowVisibility[window]) + drawWindow(m_blitter.data(), screen(), window); } m_blitter->release(); @@ -861,70 +819,37 @@ 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; - - bool top = mode == QWasmCompositor::ResizeTopLeft || - mode == QWasmCompositor::ResizeTop || - mode == QWasmCompositor::ResizeTopRight; - - 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& 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; + + 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))); + + 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) { QWindow *modalWindow; - bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow); + bool isTargetWindowBlocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow); - if (blocked) { + if (isTargetWindowBlocked) { modalWindow->requestActivate(); - raise(static_cast<QWasmWindow*>(modalWindow->handle())); + raise(AsWasmWindow(modalWindow)); return; } @@ -947,12 +872,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) @@ -974,231 +893,290 @@ 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) { - QPoint targetPoint(mouseEvent->targetX, mouseEvent->targetY); - QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; + if (event.pointerType != PointerType::Mouse) + return false; - QEvent::Type buttonEventType = QEvent::None; - Qt::MouseButton button = Qt::NoButton; - Qt::KeyboardModifiers modifiers = eventTranslator->translateMouseEventModifier(mouseEvent); + const QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + event.point; - QWindow *window2 = nullptr; - if (resizeMode == QWasmCompositor::ResizeNone) - window2 = screen()->compositor()->windowAt(globalPoint, 5); + QWindow *const targetWindow = ([this, &targetPointInScreenCoords]() -> QWindow * { + auto *targetWindow = m_mouseCaptureWindow != nullptr ? m_mouseCaptureWindow.get() + : m_windowManipulation.operation() == WindowManipulation::Operation::None + ? screen()->compositor()->windowAt(targetPointInScreenCoords, 5) + : nullptr; - if (window2 == nullptr) { - window2 = lastWindow; - } else { - lastWindow = window2; - } + return targetWindow ? targetWindow : m_lastMouseTargetWindow.get(); + })(); + if (targetWindow) + m_lastMouseTargetWindow = targetWindow; - QPoint localPoint = window2->mapFromGlobal(globalPoint); - bool interior = window2->geometry().contains(globalPoint); - bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window2); + const QPoint pointInTargetWindowCoords = targetWindow->mapFromGlobal(targetPointInScreenCoords); + const bool pointerIsWithinTargetWindowBounds = targetWindow->geometry().contains(targetPointInScreenCoords); + const bool isTargetWindowBlocked = QGuiApplicationPrivate::instance()->isWindowBlocked(targetWindow); - if (mouseInCanvas) { - if (windowUnderMouse != window2 && interior) { + if (m_mouseInCanvas && m_windowUnderMouse != targetWindow && pointerIsWithinTargetWindowBounds) { // delayed mouse enter - enterWindow(window2, localPoint, globalPoint); - windowUnderMouse = window2; - } + enterWindow(targetWindow, pointInTargetWindowCoords, targetPointInScreenCoords); + m_windowUnderMouse = targetWindow; } - QWasmWindow *htmlWindow = static_cast<QWasmWindow*>(window2->handle()); - Qt::WindowStates windowState = htmlWindow->window()->windowState(); - bool isResizable = !(windowState.testFlag(Qt::WindowMaximized) || - windowState.testFlag(Qt::WindowFullScreen)); + QWasmWindow *wasmTargetWindow = AsWasmWindow(targetWindow); + 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: { - button = QWasmEventTranslator::translateMouseButton(mouseEvent->button); - - if (window2) - window2->requestActivate(); - - pressedButtons.setFlag(button); - - pressedWindow = window2; - buttonEventType = QEvent::MouseButtonPress; - - // button overview: - // 0 = primary mouse button, usually left click - // 1 = middle mouse button, usually mouse wheel - // 2 = right mouse button, usually right click - // from: https://w3c.github.io/uievents/#dom-mouseevent-button - if (mouseEvent->button == 0) { - if (!blocked && !(htmlWindow->m_windowState & Qt::WindowFullScreen) - && !(htmlWindow->m_windowState & Qt::WindowMaximized)) { - if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) - && htmlWindow->isPointOnTitle(globalPoint)) - draggedWindow = window2; - else if (htmlWindow && htmlWindow->isPointOnResizeRegion(globalPoint)) { - draggedWindow = window2; - resizeMode = htmlWindow->resizeModeAtPoint(globalPoint); - resizePoint = globalPoint; - resizeStartRect = window2->geometry(); - } - } - } + screen()->canvas().call<void>("setPointerCapture", event.pointerId); + + if (targetWindow) + targetWindow->requestActivate(); + + m_pressedWindow = targetWindow; - htmlWindow->injectMousePressed(localPoint, globalPoint, button, modifiers); + m_windowManipulation.onPointerDown(event, targetWindow); + + wasmTargetWindow->injectMousePressed(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers); break; } - case EMSCRIPTEN_EVENT_MOUSEUP: + case EventType::PointerUp: { - button = QWasmEventTranslator::translateMouseButton(mouseEvent->button); - pressedButtons.setFlag(button, false); - buttonEventType = QEvent::MouseButtonRelease; - QWasmWindow *oldWindow = nullptr; - - if (mouseEvent->button == 0 && pressedWindow) { - oldWindow = static_cast<QWasmWindow*>(pressedWindow->handle()); - pressedWindow = nullptr; - } + screen()->canvas().call<void>("releasePointerCapture", event.pointerId); - if (draggedWindow && pressedButtons.testFlag(Qt::NoButton)) { - draggedWindow = nullptr; - resizeMode = QWasmCompositor::ResizeNone; - } + m_windowManipulation.onPointerUp(event); - if (oldWindow) - oldWindow->injectMouseReleased(localPoint, globalPoint, button, modifiers); - else - htmlWindow->injectMouseReleased(localPoint, globalPoint, button, modifiers); + if (m_pressedWindow) { + // Always deliver the released event to the same window that was pressed + AsWasmWindow(m_pressedWindow)->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers); + if (event.mouseButton == Qt::MouseButton::LeftButton) + m_pressedWindow = nullptr; + } else { + wasmTargetWindow->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers); + } break; } - case EMSCRIPTEN_EVENT_MOUSEMOVE: // drag event + case EventType::PointerMove: { - buttonEventType = QEvent::MouseMove; - - if (htmlWindow && pressedButtons.testFlag(Qt::NoButton)) { - - bool isOnResizeRegion = htmlWindow->isPointOnResizeRegion(globalPoint); + if (wasmTargetWindow && event.mouseButtons.testFlag(Qt::NoButton)) { + const bool isOnResizeRegion = wasmTargetWindow->isPointOnResizeRegion(targetPointInScreenCoords); - if (isResizable && isOnResizeRegion && !blocked) { - QCursor resizingCursor = eventTranslator->cursorForMode(htmlWindow->resizeModeAtPoint(globalPoint)); + if (isTargetWindowResizable && isOnResizeRegion && !isTargetWindowBlocked) { + const QCursor resizingCursor = QWasmEventTranslator::cursorForMode( + wasmTargetWindow->resizeModeAtPoint(targetPointInScreenCoords)); - if (resizingCursor != window2->cursor()) { - isCursorOverridden = true; - QWasmCursor::setOverrideWasmCursor(&resizingCursor, window2->screen()); - } - } else { // off resizing area - if (isCursorOverridden) { - isCursorOverridden = false; - QWasmCursor::clearOverrideWasmCursor(window2->screen()); + if (resizingCursor != targetWindow->cursor()) { + m_isResizeCursorDisplayed = true; + QWasmCursor::setOverrideWasmCursor(resizingCursor, targetWindow->screen()); } + } else if (m_isResizeCursorDisplayed) { // off resizing area + m_isResizeCursorDisplayed = false; + QWasmCursor::clearOverrideWasmCursor(targetWindow->screen()); } } - if (!(htmlWindow->m_windowState & Qt::WindowFullScreen) && !(htmlWindow->m_windowState & Qt::WindowMaximized)) { - if (resizeMode == QWasmCompositor::ResizeNone && draggedWindow) { - draggedWindow->setX(draggedWindow->x() + mouseEvent->movementX); - draggedWindow->setY(draggedWindow->y() + mouseEvent->movementY); - } - - if (resizeMode != QWasmCompositor::ResizeNone && !(htmlWindow->m_windowState & Qt::WindowFullScreen)) { - QPoint delta = QPoint(mouseEvent->targetX, mouseEvent->targetY) - resizePoint; - resizeWindow(draggedWindow, resizeMode, resizeStartRect, delta); - } - } + m_windowManipulation.onPointerMove(event); + if (m_windowManipulation.operation() != WindowManipulation::Operation::None) + requestUpdate(); break; } - case EMSCRIPTEN_EVENT_MOUSEENTER: - processMouseEnter(mouseEvent); + case EventType::PointerEnter: + processMouseEnter(nullptr); break; - case EMSCRIPTEN_EVENT_MOUSELEAVE: - processMouseLeave(); + case EventType::PointerLeave: + processMouseLeave(); + break; + default: break; - default: break; }; - if (!interior && pressedButtons.testFlag(Qt::NoButton)) { - leaveWindow(lastWindow); + if (!pointerIsWithinTargetWindowBounds && event.mouseButtons.testFlag(Qt::NoButton)) { + leaveWindow(m_lastMouseTargetWindow); } - if (!window2 && buttonEventType == QEvent::MouseButtonRelease) { - window2 = lastWindow; - lastWindow = nullptr; - interior = true; + const bool eventAccepted = deliverEventToTarget(event, targetWindow); + if (!eventAccepted && event.type == EventType::PointerDown) + QGuiApplicationPrivate::instance()->closeAllPopups(); + return eventAccepted; +} + +bool QWasmCompositor::deliverEventToTarget(const PointerEvent &event, QWindow *eventTarget) +{ + Q_ASSERT(!m_mouseCaptureWindow || m_mouseCaptureWindow.get() == eventTarget); + + const QPoint pointInScreenCoords = screen()->geometry().topLeft() + event.point; + const QPoint targetPointClippedToScreen( + std::max(screen()->geometry().left(), + std::min(screen()->geometry().right(), pointInScreenCoords.x())), + std::max(screen()->geometry().top(), + std::min(screen()->geometry().bottom(), pointInScreenCoords.y()))); + + bool deliveringToPreviouslyClickedWindow = false; + + if (!eventTarget) { + if (event.type != EventType::PointerUp || !m_lastMouseTargetWindow) + return false; + + eventTarget = m_lastMouseTargetWindow; + m_lastMouseTargetWindow = nullptr; + deliveringToPreviouslyClickedWindow = true; } - bool accepted = false; - if (window2 && interior) { - accepted = QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>( - window2, QWasmIntegration::getTimestamp(), localPoint, globalPoint, pressedButtons, button, buttonEventType, modifiers); + + WindowArea windowArea = WindowArea::Client; + if (!deliveringToPreviouslyClickedWindow && !m_mouseCaptureWindow + && !eventTarget->geometry().contains(targetPointClippedToScreen)) { + if (!eventTarget->frameGeometry().contains(targetPointClippedToScreen)) + return false; + windowArea = WindowArea::NonClient; } - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN && !accepted) - QGuiApplicationPrivate::instance()->closeAllPopups(); - return accepted; + const QEvent::Type eventType = + MouseEvent::mouseEventTypeFromEventType(event.type, windowArea); + + return eventType != QEvent::None && + QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>( + eventTarget, QWasmIntegration::getTimestamp(), + eventTarget->mapFromGlobal(targetPointClippedToScreen), + targetPointClippedToScreen, event.mouseButtons, event.mouseButton, + eventType, event.modifiers); } -bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent) +QWasmCompositor::WindowManipulation::WindowManipulation(QWasmScreen *screen) + : m_screen(screen) { - Qt::Key qtKey; - QString keyText; - QEvent::Type keyType = QEvent::None; - switch (eventType) { - case EMSCRIPTEN_EVENT_KEYPRESS: - case EMSCRIPTEN_EVENT_KEYDOWN: // down - keyType = QEvent::KeyPress; - qtKey = this->eventTranslator->getKey(keyEvent); - keyText = this->eventTranslator->getKeyText(keyEvent, qtKey); - break; - case EMSCRIPTEN_EVENT_KEYUP: // up - keyType = QEvent::KeyRelease; - this->eventTranslator->setStickyDeadKey(keyEvent); - break; - default: - break; - }; + Q_ASSERT(!!screen); +} - if (keyType == QEvent::None) - return 0; +QWasmCompositor::WindowManipulation::Operation QWasmCompositor::WindowManipulation::operation() const +{ + if (!m_state) + return Operation::None; - QFlags<Qt::KeyboardModifier> modifiers = eventTranslator->translateKeyboardEventModifier(keyEvent); + return std::holds_alternative<MoveState>(m_state->operationSpecific) + ? Operation::Move : Operation::Resize; +} - // Clipboard fallback path: cut/copy/paste are handled by clipboard event - // handlers if direct clipboard access is not available. - if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi && modifiers & Qt::ControlModifier && - (qtKey == Qt::Key_X || qtKey == Qt::Key_C || qtKey == Qt::Key_V)) { - if (qtKey == Qt::Key_V) { - QWasmIntegration::get()->getWasmClipboard()->isPaste = true; - } - return false; - } +void QWasmCompositor::WindowManipulation::onPointerDown( + const PointerEvent& event, QWindow* windowAtPoint) +{ + // Only one operation at a time. + if (operation() != Operation::None) + return; - bool accepted = false; + 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 (keyType == QEvent::KeyPress && - modifiers.testFlag(Qt::ControlModifier) - && qtKey == Qt::Key_V) { - QWasmIntegration::get()->getWasmClipboard()->isPaste = true; - accepted = false; // continue on to event + 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 { - if (keyText.isEmpty()) - keyText = QString(keyEvent->key); - if (keyText.size() > 1) - keyText.clear(); - accepted = QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( - 0, keyType, qtKey, modifiers, keyText); + return; } - if (keyType == QEvent::KeyPress && - modifiers.testFlag(Qt::ControlModifier) - && qtKey == Qt::Key_C) { - QWasmIntegration::get()->getWasmClipboard()->isPaste = false; - accepted = false; // continue on to event + + m_state.reset(new OperationState{ + .pointerId = event.pointerId, + .window = windowAtPoint, + .operationSpecific = std::move(*operationSpecific), + }); +} + +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->translateAndClipGlobalPoint(event.point); + 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: { + const auto pointInScreenCoords = m_screen->geometry().topLeft() + event.point; + resizeWindow(pointInScreenCoords - + std::get<ResizeState>(m_state->operationSpecific).m_originInScreenCoords); + break; + } + case Operation::None: + Q_ASSERT(0); + break; } +} - return accepted; +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; + Q_ASSERT(eventType == EMSCRIPTEN_EVENT_KEYDOWN || eventType == EMSCRIPTEN_EVENT_KEYUP); + + auto translatedEvent = m_eventTranslator->translateKeyEvent(eventType, emKeyEvent); + + const QFlags<Qt::KeyboardModifier> modifiers = KeyboardModifier::getForEvent(*emKeyEvent); + + const auto clipboardResult = QWasmIntegration::get()->getWasmClipboard()->processKeyboard( + translatedEvent, modifiers); + + using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult; + if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded) + return ProceedToNativeEvent; + + if (translatedEvent.text.isEmpty()) + translatedEvent.text = QString(emKeyEvent->key); + if (translatedEvent.text.size() > 1) + translatedEvent.text.clear(); + const auto result = + QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( + 0, translatedEvent.type, translatedEvent.key, modifiers, translatedEvent.text); + return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded + ? ProceedToNativeEvent + : result; } bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wheelEvent) { Q_UNUSED(eventType); - EmscriptenMouseEvent mouseEvent = wheelEvent->mouse; + const EmscriptenMouseEvent* mouseEvent = &wheelEvent->mouse; int scrollFactor = 0; switch (wheelEvent->deltaMode) { @@ -1215,14 +1193,14 @@ bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wh scrollFactor = -scrollFactor; // Web scroll deltas are inverted from Qt deltas. - Qt::KeyboardModifiers modifiers = eventTranslator->translateMouseEventModifier(&mouseEvent); - QPoint targetPoint(mouseEvent.targetX, mouseEvent.targetY); - QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; + Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(*mouseEvent); + QPoint targetPointInCanvasCoords(mouseEvent->targetX, mouseEvent->targetY); + QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords; - QWindow *window2 = screen()->compositor()->windowAt(globalPoint, 5); - if (!window2) + QWindow *targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5); + if (!targetWindow) return 0; - QPoint localPoint = window2->mapFromGlobal(globalPoint); + QPoint pointInTargetWindowCoords = targetWindow->mapFromGlobal(targetPointInScreenCoords); QPoint pixelDelta; @@ -1232,8 +1210,8 @@ bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wh QPoint angleDelta = pixelDelta; // FIXME: convert from pixels? bool accepted = QWindowSystemInterface::handleWheelEvent( - window2, QWasmIntegration::getTimestamp(), localPoint, - globalPoint, pixelDelta, angleDelta, modifiers, + targetWindow, QWasmIntegration::getTimestamp(), pointInTargetWindowCoords, + targetPointInScreenCoords, pixelDelta, angleDelta, modifiers, Qt::NoScrollPhase, Qt::MouseEventNotSynthesized, g_scrollingInvertedFromDevice); return accepted; @@ -1243,17 +1221,17 @@ int QWasmCompositor::handleTouch(int eventType, const EmscriptenTouchEvent *touc { QList<QWindowSystemInterface::TouchPoint> touchPointList; touchPointList.reserve(touchEvent->numTouches); - QWindow *window2; + QWindow *targetWindow; for (int i = 0; i < touchEvent->numTouches; i++) { const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; - QPoint targetPoint(touches->targetX, touches->targetY); - QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; + QPoint targetPointInCanvasCoords(touches->targetX, touches->targetY); + QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords; - window2 = this->screen()->compositor()->windowAt(globalPoint, 5); - if (window2 == nullptr) + targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5); + if (targetWindow == nullptr) continue; QWindowSystemInterface::TouchPoint touchPoint; @@ -1262,41 +1240,41 @@ int QWasmCompositor::handleTouch(int eventType, const EmscriptenTouchEvent *touc touchPoint.id = touches->identifier; touchPoint.pressure = 1.0; - touchPoint.area.moveCenter(globalPoint); + touchPoint.area.moveCenter(targetPointInScreenCoords); - const auto tp = pressedTouchIds.constFind(touchPoint.id); - if (tp != pressedTouchIds.constEnd()) + const auto tp = m_pressedTouchIds.constFind(touchPoint.id); + if (tp != m_pressedTouchIds.constEnd()) touchPoint.normalPosition = tp.value(); - QPointF localPoint = QPointF(window2->mapFromGlobal(globalPoint)); - QPointF normalPosition(localPoint.x() / window2->width(), - localPoint.y() / window2->height()); + QPointF pointInTargetWindowCoords = QPointF(targetWindow->mapFromGlobal(targetPointInScreenCoords)); + QPointF normalPosition(pointInTargetWindowCoords.x() / targetWindow->width(), + pointInTargetWindowCoords.y() / targetWindow->height()); const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition); touchPoint.normalPosition = normalPosition; switch (eventType) { case EMSCRIPTEN_EVENT_TOUCHSTART: - if (tp != pressedTouchIds.constEnd()) { + if (tp != m_pressedTouchIds.constEnd()) { touchPoint.state = (stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated); } else { touchPoint.state = QEventPoint::State::Pressed; } - pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); + m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); break; case EMSCRIPTEN_EVENT_TOUCHEND: touchPoint.state = QEventPoint::State::Released; - pressedTouchIds.remove(touchPoint.id); + m_pressedTouchIds.remove(touchPoint.id); break; case EMSCRIPTEN_EVENT_TOUCHMOVE: touchPoint.state = (stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated); - pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); + m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); break; default: break; @@ -1305,40 +1283,51 @@ int QWasmCompositor::handleTouch(int eventType, const EmscriptenTouchEvent *touc touchPointList.append(touchPoint); } - QFlags<Qt::KeyboardModifier> keyModifier = eventTranslator->translateTouchEventModifier(touchEvent); + QFlags<Qt::KeyboardModifier> keyModifier = KeyboardModifier::getForEvent(*touchEvent); bool accepted = false; if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) - accepted = QWindowSystemInterface::handleTouchCancelEvent(window2, QWasmIntegration::getTimestamp(), touchDevice, keyModifier); + accepted = QWindowSystemInterface::handleTouchCancelEvent(targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), keyModifier); else accepted = QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( - window2, QWasmIntegration::getTimestamp(), touchDevice, touchPointList, keyModifier); + targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), touchPointList, keyModifier); return static_cast<int>(accepted); } +void QWasmCompositor::setCapture(QWasmWindow *window) +{ + Q_ASSERT(std::find(m_windowStack.begin(), m_windowStack.end(), window) != m_windowStack.end()); + m_mouseCaptureWindow = window->window(); +} + +void QWasmCompositor::releaseCapture() +{ + m_mouseCaptureWindow = nullptr; +} + void QWasmCompositor::leaveWindow(QWindow *window) { - windowUnderMouse = nullptr; + m_windowUnderMouse = nullptr; QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>(window); } -void QWasmCompositor::enterWindow(QWindow *window, const QPoint &localPoint, const QPoint &globalPoint) +void QWasmCompositor::enterWindow(QWindow *window, const QPoint &pointInTargetWindowCoords, const QPoint &targetPointInScreenCoords) { - QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::SynchronousDelivery>(window, localPoint, globalPoint); + QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::SynchronousDelivery>(window, pointInTargetWindowCoords, targetPointInScreenCoords); } bool QWasmCompositor::processMouseEnter(const EmscriptenMouseEvent *mouseEvent) { Q_UNUSED(mouseEvent) // mouse has entered the canvas area - mouseInCanvas = true; + m_mouseInCanvas = true; return true; } bool QWasmCompositor::processMouseLeave() { - mouseInCanvas = false; + m_mouseInCanvas = false; return true; } |