Move the window through the title bar element itself
The compositor is redundant in the process of moving the window. Have the title bar react to move all by itself. Additionally, a clearer structure in the window was introduced. The non-client area has been extracted into a separate class, as was the icon store and free DOM functions used across files. Since it was now easy, made the window maximize/restore on double click on the title element. Fixes: QTBUG-107626 Change-Id: Iba7f207e46806ae7162656965892ae5a48ac5ebe Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io> (cherry picked from commit 32666691c21cc91a3d7c7585dad711dc9743fdce) Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io>
This commit is contained in:
parent
a9e16b24b9
commit
33895a9b6d
@ -15,10 +15,12 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
|
||||
SOURCES
|
||||
main.cpp
|
||||
qwasmaccessibility.cpp qwasmaccessibility.h
|
||||
qwasmbase64iconstore.cpp qwasmbase64iconstore.h
|
||||
qwasmclipboard.cpp qwasmclipboard.h
|
||||
qwasmcompositor.cpp qwasmcompositor.h
|
||||
qwasmcssstyle.cpp qwasmcssstyle.h
|
||||
qwasmcursor.cpp qwasmcursor.h
|
||||
qwasmdom.cpp qwasmdom.h
|
||||
qwasmevent.cpp qwasmevent.h
|
||||
qwasmeventdispatcher.cpp qwasmeventdispatcher.h
|
||||
qwasmeventtranslator.cpp qwasmeventtranslator.h
|
||||
@ -33,6 +35,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
|
||||
qwasmstylepixmaps_p.h
|
||||
qwasmtheme.cpp qwasmtheme.h
|
||||
qwasmwindow.cpp qwasmwindow.h
|
||||
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
|
||||
qwasminputcontext.cpp qwasminputcontext.h
|
||||
qwasmdrag.cpp qwasmdrag.h
|
||||
qwasmwindowstack.cpp qwasmwindowstack.h
|
||||
|
40
src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
Normal file
40
src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "qwasmbase64iconstore.h"
|
||||
|
||||
#include <QtCore/qfile.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_GLOBAL_STATIC(Base64IconStore, globalWasmWindowIconStore);
|
||||
|
||||
Base64IconStore::Base64IconStore()
|
||||
{
|
||||
QString iconSources[static_cast<size_t>(IconType::Size)] = {
|
||||
QStringLiteral(":/wasm-window/maximize.svg"), QStringLiteral(":/wasm-window/qtlogo.svg"),
|
||||
QStringLiteral(":/wasm-window/restore.svg"), QStringLiteral(":/wasm-window/x.svg")
|
||||
};
|
||||
|
||||
for (size_t iconType = static_cast<size_t>(IconType::First);
|
||||
iconType < static_cast<size_t>(IconType::Size); ++iconType) {
|
||||
QFile svgFile(iconSources[static_cast<size_t>(iconType)]);
|
||||
if (!svgFile.open(QIODevice::ReadOnly))
|
||||
Q_ASSERT(false); // A resource should always be opened.
|
||||
m_storage[static_cast<size_t>(iconType)] = svgFile.readAll().toBase64();
|
||||
}
|
||||
}
|
||||
|
||||
Base64IconStore::~Base64IconStore() = default;
|
||||
|
||||
Base64IconStore *Base64IconStore::get()
|
||||
{
|
||||
return globalWasmWindowIconStore();
|
||||
}
|
||||
|
||||
std::string_view Base64IconStore::getIcon(IconType type) const
|
||||
{
|
||||
return m_storage[static_cast<size_t>(type)];
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
36
src/plugins/platforms/wasm/qwasmbase64iconstore.h
Normal file
36
src/plugins/platforms/wasm/qwasmbase64iconstore.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef QWASMBASE64IMAGESTORE_H
|
||||
#define QWASMBASE64IMAGESTORE_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class Base64IconStore
|
||||
{
|
||||
public:
|
||||
enum class IconType {
|
||||
Maximize,
|
||||
First = Maximize,
|
||||
QtLogo,
|
||||
Restore,
|
||||
X,
|
||||
Size,
|
||||
};
|
||||
|
||||
Base64IconStore();
|
||||
~Base64IconStore();
|
||||
|
||||
static Base64IconStore *get();
|
||||
|
||||
std::string_view getIcon(IconType type) const;
|
||||
|
||||
private:
|
||||
std::string m_storage[static_cast<size_t>(IconType::Size)];
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
#endif // QWASMBASE64IMAGESTORE_H
|
@ -44,7 +44,6 @@ EMSCRIPTEN_BINDINGS(qtMouseModule) {
|
||||
|
||||
QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
|
||||
: QObject(screen),
|
||||
m_windowManipulation(screen),
|
||||
m_windowStack(std::bind(&QWasmCompositor::onTopWindowChanged, this)),
|
||||
m_eventTranslator(std::make_unique<QWasmEventTranslator>())
|
||||
{
|
||||
@ -374,10 +373,9 @@ bool QWasmCompositor::processPointer(const PointerEvent& event)
|
||||
const auto pointInScreen = screen()->mapFromLocal(event.localPoint);
|
||||
|
||||
QWindow *const targetWindow = ([this, pointInScreen]() -> QWindow * {
|
||||
auto *targetWindow = m_mouseCaptureWindow != nullptr ? m_mouseCaptureWindow.get()
|
||||
: m_windowManipulation.operation() == WindowManipulation::Operation::None
|
||||
? screen()->compositor()->windowAt(pointInScreen, 5)
|
||||
: nullptr;
|
||||
auto *targetWindow = m_mouseCaptureWindow != nullptr
|
||||
? m_mouseCaptureWindow.get()
|
||||
: windowAt(pointInScreen, 5);
|
||||
|
||||
return targetWindow ? targetWindow : m_lastMouseTargetWindow.get();
|
||||
})();
|
||||
@ -403,20 +401,12 @@ bool QWasmCompositor::processPointer(const PointerEvent& event)
|
||||
if (targetWindow)
|
||||
targetWindow->requestActivate();
|
||||
|
||||
m_windowManipulation.onPointerDown(event, targetWindow);
|
||||
break;
|
||||
}
|
||||
case EventType::PointerUp:
|
||||
{
|
||||
screen()->element().call<void>("releasePointerCapture", event.pointerId);
|
||||
|
||||
m_windowManipulation.onPointerUp(event);
|
||||
break;
|
||||
}
|
||||
case EventType::PointerMove: {
|
||||
m_windowManipulation.onPointerMove(event);
|
||||
if (m_windowManipulation.operation() != WindowManipulation::Operation::None)
|
||||
requestUpdate();
|
||||
break;
|
||||
}
|
||||
case EventType::PointerEnter:
|
||||
@ -481,80 +471,6 @@ bool QWasmCompositor::deliverEventToTarget(const PointerEvent &event, QWindow *e
|
||||
eventType, event.modifiers);
|
||||
}
|
||||
|
||||
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 Operation::Move;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!asWasmWindow(windowAtPoint)->isPointOnTitle(event.pointInViewport))
|
||||
return;
|
||||
|
||||
m_state.reset(new OperationState{ .pointerId = event.pointerId,
|
||||
.window = windowAtPoint,
|
||||
.lastPointInScreenCoords =
|
||||
m_screen->mapFromLocal(event.localPoint) });
|
||||
}
|
||||
|
||||
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->clipPoint(m_screen->mapFromLocal(event.localPoint));
|
||||
const QPoint difference = targetPointClippedToScreen - m_state->lastPointInScreenCoords;
|
||||
|
||||
m_state->lastPointInScreenCoords = targetPointClippedToScreen;
|
||||
|
||||
m_state->window->setPosition(m_state->window->position() + difference);
|
||||
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();
|
||||
}
|
||||
|
||||
bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *emKeyEvent)
|
||||
{
|
||||
constexpr bool ProceedToNativeEvent = false;
|
||||
|
@ -65,34 +65,6 @@ public:
|
||||
void handleBackingStoreFlush(QWindow *window);
|
||||
|
||||
private:
|
||||
class WindowManipulation {
|
||||
public:
|
||||
enum class Operation {
|
||||
None,
|
||||
Move,
|
||||
};
|
||||
|
||||
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 OperationState
|
||||
{
|
||||
int pointerId;
|
||||
QPointer<QWindow> window;
|
||||
QPoint lastPointInScreenCoords;
|
||||
};
|
||||
|
||||
QWasmScreen *m_screen;
|
||||
|
||||
std::unique_ptr<OperationState> m_state;
|
||||
};
|
||||
|
||||
void frame(bool all, const QList<QWasmWindow *> &windows);
|
||||
|
||||
void onTopWindowChanged();
|
||||
@ -124,7 +96,6 @@ private:
|
||||
|
||||
void updateEnabledState();
|
||||
|
||||
WindowManipulation m_windowManipulation;
|
||||
QWasmWindowStack m_windowStack;
|
||||
|
||||
bool m_isEnabled = true;
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "qwasmcssstyle.h"
|
||||
|
||||
#include "qwasmbase64iconstore.h"
|
||||
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qfile.h>
|
||||
|
||||
@ -117,6 +119,7 @@ const char *Style = R"css(
|
||||
overflow: hidden;
|
||||
height: 18px;
|
||||
padding-bottom: 4px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.qt-window.has-title-bar .title-bar {
|
||||
@ -147,6 +150,10 @@ const char *Style = R"css(
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.title-bar div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.title-bar .image-button {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@ -156,7 +163,7 @@ const char *Style = R"css(
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-bar .image-button span {
|
||||
.title-bar .image-button img {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
user-select: none;
|
||||
@ -165,19 +172,19 @@ const char *Style = R"css(
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
|
||||
.title-bar .image-button span[qt-builtin-image-type=x] {
|
||||
.title-bar .image-button img[qt-builtin-image-type=x] {
|
||||
background-image: url("data:image/svg+xml;base64,$close_icon");
|
||||
}
|
||||
|
||||
.title-bar .image-button span[qt-builtin-image-type=qt-logo] {
|
||||
.title-bar .image-button img[qt-builtin-image-type=qt-logo] {
|
||||
background-image: url("qtlogo.svg");
|
||||
}
|
||||
|
||||
.title-bar .image-button span[qt-builtin-image-type=restore] {
|
||||
.title-bar .image-button img[qt-builtin-image-type=restore] {
|
||||
background-image: url("data:image/svg+xml;base64,$restore_icon");
|
||||
}
|
||||
|
||||
.title-bar .image-button span[qt-builtin-image-type=maximize] {
|
||||
.title-bar .image-button img[qt-builtin-image-type=maximize] {
|
||||
background-image: url("data:image/svg+xml;base64,$maximize_icon");
|
||||
}
|
||||
.title-bar .action-button {
|
||||
@ -185,60 +192,24 @@ const char *Style = R"css(
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.qt-window.blocked .title-bar .action-button {
|
||||
.qt-window.blocked div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.title-bar .action-button span {
|
||||
.title-bar .action-button img {
|
||||
transition: filter 0.08s ease-out;
|
||||
}
|
||||
|
||||
.title-bar .action-button:hover span {
|
||||
.title-bar .action-button:hover img {
|
||||
filter: invert(0.45);
|
||||
}
|
||||
|
||||
.title-bar .action-button:active span {
|
||||
.title-bar .action-button:active img {
|
||||
filter: invert(0.6);
|
||||
}
|
||||
|
||||
)css";
|
||||
|
||||
class Base64IconStore
|
||||
{
|
||||
public:
|
||||
enum class IconType {
|
||||
Maximize,
|
||||
First = Maximize,
|
||||
QtLogo,
|
||||
Restore,
|
||||
X,
|
||||
Size,
|
||||
};
|
||||
|
||||
Base64IconStore()
|
||||
{
|
||||
QString iconSources[static_cast<size_t>(IconType::Size)] = {
|
||||
QStringLiteral(":/wasm-window/maximize.svg"),
|
||||
QStringLiteral(":/wasm-window/qtlogo.svg"), QStringLiteral(":/wasm-window/restore.svg"),
|
||||
QStringLiteral(":/wasm-window/x.svg")
|
||||
};
|
||||
|
||||
for (size_t iconType = static_cast<size_t>(IconType::First);
|
||||
iconType < static_cast<size_t>(IconType::Size); ++iconType) {
|
||||
QFile svgFile(iconSources[static_cast<size_t>(iconType)]);
|
||||
if (!svgFile.open(QIODevice::ReadOnly))
|
||||
Q_ASSERT(false); // A resource should always be opened.
|
||||
m_storage[static_cast<size_t>(iconType)] = svgFile.readAll().toBase64();
|
||||
}
|
||||
}
|
||||
~Base64IconStore() = default;
|
||||
|
||||
std::string_view getIcon(IconType type) const { return m_storage[static_cast<size_t>(type)]; }
|
||||
|
||||
private:
|
||||
std::string m_storage[static_cast<size_t>(IconType::Size)];
|
||||
};
|
||||
|
||||
void replace(std::string &str, const std::string &from, const std::string_view &to)
|
||||
{
|
||||
str.replace(str.find(from), from.length(), to);
|
||||
@ -247,13 +218,14 @@ void replace(std::string &str, const std::string &from, const std::string_view &
|
||||
|
||||
emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent)
|
||||
{
|
||||
Base64IconStore store;
|
||||
auto document = parent["ownerDocument"];
|
||||
auto screenStyle = document.call<emscripten::val>("createElement", emscripten::val("style"));
|
||||
auto text = std::string(Style);
|
||||
replace(text, "$close_icon", store.getIcon(Base64IconStore::IconType::X));
|
||||
replace(text, "$restore_icon", store.getIcon(Base64IconStore::IconType::Restore));
|
||||
replace(text, "$maximize_icon", store.getIcon(Base64IconStore::IconType::Maximize));
|
||||
|
||||
using IconType = Base64IconStore::IconType;
|
||||
replace(text, "$close_icon", Base64IconStore::get()->getIcon(IconType::X));
|
||||
replace(text, "$restore_icon", Base64IconStore::get()->getIcon(IconType::Restore));
|
||||
replace(text, "$maximize_icon", Base64IconStore::get()->getIcon(IconType::Maximize));
|
||||
|
||||
screenStyle.set("textContent", text);
|
||||
return screenStyle;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#define QWASMCURSOR_H
|
||||
|
||||
#include <qpa/qplatformcursor.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QWasmCursor : public QPlatformCursor
|
||||
|
36
src/plugins/platforms/wasm/qwasmdom.cpp
Normal file
36
src/plugins/platforms/wasm/qwasmdom.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "qwasmdom.h"
|
||||
|
||||
#include <QtCore/qpoint.h>
|
||||
#include <QtCore/qrect.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace dom {
|
||||
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
|
||||
{
|
||||
if (flag) {
|
||||
element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
|
||||
return;
|
||||
}
|
||||
|
||||
element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
|
||||
}
|
||||
|
||||
QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point)
|
||||
{
|
||||
auto sourceBoundingRect =
|
||||
QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
|
||||
auto targetBoundingRect =
|
||||
QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
|
||||
|
||||
auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
|
||||
return (point + offset).toPoint();
|
||||
}
|
||||
} // namespace dom
|
||||
|
||||
QT_END_NAMESPACE
|
29
src/plugins/platforms/wasm/qwasmdom.h
Normal file
29
src/plugins/platforms/wasm/qwasmdom.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef QWASMDOM_H
|
||||
#define QWASMDOM_H
|
||||
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QPoint;
|
||||
|
||||
namespace dom {
|
||||
inline emscripten::val document()
|
||||
{
|
||||
return emscripten::val::global("document");
|
||||
}
|
||||
|
||||
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag);
|
||||
|
||||
QPoint mapPoint(emscripten::val source, emscripten::val target, const QPoint &point);
|
||||
} // namespace dom
|
||||
|
||||
QT_END_NAMESPACE
|
||||
#endif // QWASMDOM_H
|
@ -39,6 +39,7 @@ std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
|
||||
return std::nullopt;
|
||||
|
||||
ret.type = *eventType;
|
||||
ret.currentTarget = event["currentTarget"];
|
||||
ret.pointerType = event["pointerType"].as<std::string>() == "mouse" ?
|
||||
PointerType::Mouse : PointerType::Other;
|
||||
ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
|
||||
|
@ -110,6 +110,7 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
|
||||
struct Q_CORE_EXPORT Event
|
||||
{
|
||||
EventType type;
|
||||
emscripten::val currentTarget = emscripten::val::undefined();
|
||||
};
|
||||
|
||||
struct Q_CORE_EXPORT MouseEvent : public Event
|
||||
|
@ -230,9 +230,8 @@ QPoint QWasmScreen::mapFromLocal(const QPoint &p) const
|
||||
|
||||
QPoint QWasmScreen::clipPoint(const QPoint &p) const
|
||||
{
|
||||
return QPoint(
|
||||
std::max(screen()->geometry().left(), std::min(screen()->geometry().right(), p.x())),
|
||||
std::max(screen()->geometry().top(), std::min(screen()->geometry().bottom(), p.y())));
|
||||
return QPoint(qBound(screen()->geometry().left(), p.x(), screen()->geometry().right()),
|
||||
qBound(screen()->geometry().top(), p.y(), screen()->geometry().bottom()));
|
||||
}
|
||||
|
||||
void QWasmScreen::invalidateSize()
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <QtGui/qopenglfunctions.h>
|
||||
#include <QBuffer>
|
||||
|
||||
#include "qwasmbase64iconstore.h"
|
||||
#include "qwasmdom.h"
|
||||
#include "qwasmwindow.h"
|
||||
#include "qwasmscreen.h"
|
||||
#include "qwasmstylepixmaps_p.h"
|
||||
@ -27,367 +29,14 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_GUI_EXPORT int qt_defaultDpiX();
|
||||
|
||||
namespace {
|
||||
enum class IconType {
|
||||
Maximize,
|
||||
First = Maximize,
|
||||
QtLogo,
|
||||
Restore,
|
||||
X,
|
||||
Size,
|
||||
};
|
||||
|
||||
void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
|
||||
{
|
||||
if (flag) {
|
||||
element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
|
||||
return;
|
||||
}
|
||||
|
||||
element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class QWasmWindow::Resizer
|
||||
{
|
||||
public:
|
||||
class ResizerElement
|
||||
{
|
||||
public:
|
||||
static constexpr const char *cssClassNameForEdges(Qt::Edges edges)
|
||||
{
|
||||
switch (edges) {
|
||||
case Qt::TopEdge | Qt::LeftEdge:;
|
||||
return "nw";
|
||||
case Qt::TopEdge:
|
||||
return "n";
|
||||
case Qt::TopEdge | Qt::RightEdge:
|
||||
return "ne";
|
||||
case Qt::LeftEdge:
|
||||
return "w";
|
||||
case Qt::RightEdge:
|
||||
return "e";
|
||||
case Qt::BottomEdge | Qt::LeftEdge:
|
||||
return "sw";
|
||||
case Qt::BottomEdge:
|
||||
return "s";
|
||||
case Qt::BottomEdge | Qt::RightEdge:
|
||||
return "se";
|
||||
default:
|
||||
Q_ASSERT(false); // notreached
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer)
|
||||
: m_element(emscripten::val::global("document")
|
||||
.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_edges(edges),
|
||||
m_resizer(resizer)
|
||||
{
|
||||
Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null");
|
||||
|
||||
m_element["classList"].call<void>("add", emscripten::val("resize-outline"));
|
||||
m_element["classList"].call<void>("add", emscripten::val(cssClassNameForEdges(edges)));
|
||||
|
||||
parentElement.call<void>("appendChild", m_element);
|
||||
|
||||
m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerdown", [this](emscripten::val event) {
|
||||
if (!onPointerDown(*PointerEvent::fromWeb(event)))
|
||||
return;
|
||||
m_resizer->onInteraction();
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
});
|
||||
m_mouseDragEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointermove", [this](emscripten::val event) {
|
||||
if (onPointerMove(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerup", [this](emscripten::val event) {
|
||||
if (onPointerUp(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
~ResizerElement()
|
||||
{
|
||||
m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
|
||||
}
|
||||
ResizerElement(const ResizerElement &other) = delete;
|
||||
ResizerElement(ResizerElement &&other) = default;
|
||||
ResizerElement &operator=(const ResizerElement &other) = delete;
|
||||
ResizerElement &operator=(ResizerElement &&other) = delete;
|
||||
|
||||
bool onPointerDown(const PointerEvent &event)
|
||||
{
|
||||
if (event.pointerType != PointerType::Mouse)
|
||||
return false;
|
||||
|
||||
m_element.call<void>("setPointerCapture", event.pointerId);
|
||||
m_capturedPointerId = event.pointerId;
|
||||
|
||||
m_resizer->startResize(m_edges, event.pointInPage);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onPointerMove(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
m_resizer->continueResize(event.pointInPage);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onPointerUp(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
m_resizer->finishResize();
|
||||
m_element.call<void>("releasePointerCapture", event.pointerId);
|
||||
m_capturedPointerId = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
emscripten::val m_element;
|
||||
|
||||
int m_capturedPointerId = -1;
|
||||
|
||||
const Qt::Edges m_edges;
|
||||
|
||||
Resizer *m_resizer;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDragEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
|
||||
};
|
||||
|
||||
using ClickCallback = std::function<void()>;
|
||||
|
||||
Resizer(QWasmWindow *window, emscripten::val parentElement) : m_window(window)
|
||||
{
|
||||
Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null");
|
||||
|
||||
constexpr std::array<int, 8> ResizeEdges = { Qt::TopEdge | Qt::LeftEdge,
|
||||
Qt::TopEdge,
|
||||
Qt::TopEdge | Qt::RightEdge,
|
||||
Qt::LeftEdge,
|
||||
Qt::RightEdge,
|
||||
Qt::BottomEdge | Qt::LeftEdge,
|
||||
Qt::BottomEdge,
|
||||
Qt::BottomEdge | Qt::RightEdge };
|
||||
std::transform(std::begin(ResizeEdges), std::end(ResizeEdges),
|
||||
std::back_inserter(m_elements), [parentElement, this](int edges) {
|
||||
return std::make_unique<ResizerElement>(parentElement,
|
||||
Qt::Edges::fromInt(edges), this);
|
||||
});
|
||||
}
|
||||
|
||||
~Resizer() = default;
|
||||
|
||||
private:
|
||||
void onInteraction() { m_window->onInteraction(); }
|
||||
|
||||
void startResize(Qt::Edges resizeEdges, const QPoint &origin)
|
||||
{
|
||||
Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress");
|
||||
|
||||
const QWindow *window = m_window->window();
|
||||
// TODO(mikolajboc): Implement system resize
|
||||
// .m_originInScreenCoords = m_systemDragInitData.lastMouseMovePoint,
|
||||
m_currentResizeData.reset(new ResizeData{
|
||||
.edges = resizeEdges,
|
||||
.originInScreenCoords = origin,
|
||||
.initialWindowBounds = window->geometry(),
|
||||
.minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
|
||||
window->minimumHeight() - window->geometry().height()),
|
||||
.maxGrow = QPoint(window->maximumWidth() - window->geometry().width(),
|
||||
window->maximumHeight() - window->geometry().height()) });
|
||||
}
|
||||
|
||||
void continueResize(const QPoint &point)
|
||||
{
|
||||
const auto amount = point - m_currentResizeData->originInScreenCoords;
|
||||
const QPoint cappedGrowVector(
|
||||
std::min(m_currentResizeData->maxGrow.x(),
|
||||
std::max(m_currentResizeData->minShrink.x(),
|
||||
(m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x()
|
||||
: (m_currentResizeData->edges & Qt::Edge::RightEdge)
|
||||
? amount.x()
|
||||
: 0)),
|
||||
std::min(m_currentResizeData->maxGrow.y(),
|
||||
std::max(m_currentResizeData->minShrink.y(),
|
||||
(m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y()
|
||||
: (m_currentResizeData->edges & Qt::Edge::BottomEdge)
|
||||
? amount.y()
|
||||
: 0)));
|
||||
|
||||
auto bounds = m_currentResizeData->initialWindowBounds.adjusted(
|
||||
(m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0);
|
||||
bounds.setY(std::max(m_window->screen()->geometry().y() + m_window->frameMargins().top(),
|
||||
bounds.y()));
|
||||
m_window->setGeometry(std::move(bounds));
|
||||
}
|
||||
|
||||
void finishResize()
|
||||
{
|
||||
Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress");
|
||||
m_currentResizeData.reset();
|
||||
}
|
||||
|
||||
struct ResizeData
|
||||
{
|
||||
Qt::Edges edges = Qt::Edges::fromInt(0);
|
||||
QPoint originInScreenCoords;
|
||||
QRect initialWindowBounds;
|
||||
QPoint minShrink;
|
||||
QPoint maxGrow;
|
||||
};
|
||||
std::unique_ptr<ResizeData> m_currentResizeData;
|
||||
|
||||
QWasmWindow *m_window;
|
||||
std::vector<std::unique_ptr<ResizerElement>> m_elements;
|
||||
};
|
||||
|
||||
class QWasmWindow::WebImageButton
|
||||
{
|
||||
public:
|
||||
class Callbacks
|
||||
{
|
||||
public:
|
||||
Callbacks() = default;
|
||||
Callbacks(std::function<void()> onInteraction, std::function<void()> onClick)
|
||||
: m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick))
|
||||
{
|
||||
Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO,
|
||||
"Both callbacks need to be either null or non-null");
|
||||
}
|
||||
~Callbacks() = default;
|
||||
|
||||
Callbacks(const Callbacks &) = delete;
|
||||
Callbacks(Callbacks &&) = default;
|
||||
Callbacks &operator=(const Callbacks &) = delete;
|
||||
Callbacks &operator=(Callbacks &&) = default;
|
||||
|
||||
operator bool() const { return !!m_onInteraction; }
|
||||
|
||||
void onInteraction() { return m_onInteraction(); }
|
||||
void onClick() { return m_onClick(); }
|
||||
|
||||
private:
|
||||
std::function<void()> m_onInteraction;
|
||||
std::function<void()> m_onClick;
|
||||
};
|
||||
|
||||
WebImageButton()
|
||||
: m_containerElement(
|
||||
emscripten::val::global("document")
|
||||
.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_imageHolderElement(
|
||||
emscripten::val::global("document")
|
||||
.call<emscripten::val>("createElement", emscripten::val("span")))
|
||||
{
|
||||
m_imageHolderElement.set("draggable", false);
|
||||
|
||||
m_containerElement["classList"].call<void>("add", emscripten::val("image-button"));
|
||||
m_containerElement.call<void>("appendChild", m_imageHolderElement);
|
||||
}
|
||||
|
||||
~WebImageButton() = default;
|
||||
|
||||
void setCallbacks(Callbacks callbacks)
|
||||
{
|
||||
if (callbacks) {
|
||||
if (!m_webClickEventCallback) {
|
||||
m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>(
|
||||
m_containerElement, "mousedown", [this](emscripten::val event) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
m_callbacks.onInteraction();
|
||||
});
|
||||
m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>(
|
||||
m_containerElement, "click",
|
||||
[this](emscripten::val) { m_callbacks.onClick(); });
|
||||
}
|
||||
} else {
|
||||
m_webMouseDownEventCallback.reset();
|
||||
m_webClickEventCallback.reset();
|
||||
}
|
||||
syncCSSClassWith(m_containerElement, "action-button", !!callbacks);
|
||||
m_callbacks = std::move(callbacks);
|
||||
}
|
||||
|
||||
void setImage(std::string_view imageData, std::string_view format)
|
||||
{
|
||||
m_imageHolderElement.call<void>("removeAttribute",
|
||||
emscripten::val("qt-builtin-image-type"));
|
||||
m_imageHolderElement["style"].set("backgroundImage",
|
||||
"url('data:image/" + std::string(format) + ";base64,"
|
||||
+ std::string(imageData) + "')");
|
||||
}
|
||||
|
||||
void setImage(IconType type)
|
||||
{
|
||||
m_imageHolderElement["style"].set("backgroundImage", emscripten::val::undefined());
|
||||
const auto imageType = ([type]() {
|
||||
switch (type) {
|
||||
case IconType::QtLogo:
|
||||
return "qt-logo";
|
||||
case IconType::X:
|
||||
return "x";
|
||||
case IconType::Restore:
|
||||
return "restore";
|
||||
case IconType::Maximize:
|
||||
return "maximize";
|
||||
default:
|
||||
return "err";
|
||||
}
|
||||
})();
|
||||
m_imageHolderElement.call<void>("setAttribute", emscripten::val("qt-builtin-image-type"),
|
||||
emscripten::val(imageType));
|
||||
}
|
||||
|
||||
void setVisible(bool visible)
|
||||
{
|
||||
m_containerElement["style"].set("display", visible ? "flex" : "none");
|
||||
}
|
||||
|
||||
emscripten::val htmlElement() const { return m_containerElement; }
|
||||
emscripten::val imageElement() const { return m_imageHolderElement; }
|
||||
|
||||
private:
|
||||
emscripten::val m_containerElement;
|
||||
emscripten::val m_imageHolderElement;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webMouseMoveEventCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webMouseDownEventCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webClickEventCallback;
|
||||
|
||||
Callbacks m_callbacks;
|
||||
};
|
||||
|
||||
QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore)
|
||||
: QPlatformWindow(w),
|
||||
m_window(w),
|
||||
m_compositor(compositor),
|
||||
m_backingStore(backingStore),
|
||||
m_document(emscripten::val::global("document")),
|
||||
m_document(dom::document()),
|
||||
m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_titleBar(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_label(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_canvasContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas")))
|
||||
@ -395,52 +44,11 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
|
||||
m_qtWindow.set("className", "qt-window");
|
||||
m_qtWindow["style"].set("display", std::string("none"));
|
||||
|
||||
m_nonClientArea = std::make_unique<NonClientArea>(this, m_qtWindow);
|
||||
m_nonClientArea->titleBar()->setTitle(window()->title());
|
||||
|
||||
m_qtWindow.call<void>("appendChild", m_windowContents);
|
||||
|
||||
m_resizer = std::make_unique<Resizer>(this, m_qtWindow);
|
||||
|
||||
m_icon = std::make_unique<WebImageButton>();
|
||||
m_icon->setImage(IconType::QtLogo);
|
||||
|
||||
m_titleBar.call<void>("appendChild", m_icon->htmlElement());
|
||||
m_titleBar.set("className", "title-bar");
|
||||
|
||||
auto spacer = m_document.call<emscripten::val>("createElement", emscripten::val("div"));
|
||||
spacer["style"].set("width", "4px");
|
||||
m_titleBar.call<void>("appendChild", spacer);
|
||||
|
||||
m_label.set("innerText", emscripten::val(window()->title().toStdString()));
|
||||
m_label.set("className", "window-name");
|
||||
|
||||
m_titleBar.call<void>("appendChild", m_label);
|
||||
|
||||
spacer = m_document.call<emscripten::val>("createElement", emscripten::val("div"));
|
||||
spacer.set("className", "spacer");
|
||||
m_titleBar.call<void>("appendChild", spacer);
|
||||
|
||||
m_restore = std::make_unique<WebImageButton>();
|
||||
m_restore->setImage(IconType::Restore);
|
||||
m_restore->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); },
|
||||
[this]() { onRestoreClicked(); }));
|
||||
|
||||
m_titleBar.call<void>("appendChild", m_restore->htmlElement());
|
||||
|
||||
m_maximize = std::make_unique<WebImageButton>();
|
||||
m_maximize->setImage(IconType::Maximize);
|
||||
m_maximize->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); },
|
||||
[this]() { onMaximizeClicked(); }));
|
||||
|
||||
m_titleBar.call<void>("appendChild", m_maximize->htmlElement());
|
||||
|
||||
m_close = std::make_unique<WebImageButton>();
|
||||
m_close->setImage(IconType::X);
|
||||
m_close->setCallbacks(WebImageButton::Callbacks([this]() { onInteraction(); },
|
||||
[this]() { onCloseClicked(); }));
|
||||
|
||||
m_titleBar.call<void>("appendChild", m_close->htmlElement());
|
||||
|
||||
m_windowContents.call<void>("appendChild", m_titleBar);
|
||||
|
||||
m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content"));
|
||||
|
||||
m_windowContents.call<void>("appendChild", m_canvasContainer);
|
||||
@ -474,6 +82,55 @@ QWasmWindow::~QWasmWindow()
|
||||
QWasmAccessibility::removeAccessibilityEnableButton(window());
|
||||
}
|
||||
|
||||
void QWasmWindow::onRestoreClicked()
|
||||
{
|
||||
window()->setWindowState(Qt::WindowNoState);
|
||||
}
|
||||
|
||||
void QWasmWindow::onMaximizeClicked()
|
||||
{
|
||||
window()->setWindowState(Qt::WindowMaximized);
|
||||
}
|
||||
|
||||
void QWasmWindow::onToggleMaximized()
|
||||
{
|
||||
window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState
|
||||
: Qt::WindowMaximized);
|
||||
}
|
||||
|
||||
void QWasmWindow::onCloseClicked()
|
||||
{
|
||||
window()->close();
|
||||
}
|
||||
|
||||
void QWasmWindow::onNonClientAreaInteraction()
|
||||
{
|
||||
if (!isActive())
|
||||
requestActivateWindow();
|
||||
}
|
||||
|
||||
bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
|
||||
{
|
||||
QPoint pointInScreen = platformScreen()->mapFromLocal(
|
||||
dom::mapPoint(event.currentTarget, platformScreen()->element(), event.localPoint));
|
||||
return QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>(
|
||||
window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
|
||||
pointInScreen, event.mouseButtons, event.mouseButton, ([event]() {
|
||||
switch (event.type) {
|
||||
case EventType::PointerDown:
|
||||
return QEvent::NonClientAreaMouseButtonPress;
|
||||
case EventType::PointerUp:
|
||||
return QEvent::NonClientAreaMouseButtonRelease;
|
||||
case EventType::PointerMove:
|
||||
return QEvent::NonClientAreaMouseMove;
|
||||
default:
|
||||
Q_ASSERT(false); // notreached
|
||||
return QEvent::None;
|
||||
}
|
||||
})(),
|
||||
event.modifiers);
|
||||
}
|
||||
|
||||
void QWasmWindow::destroy()
|
||||
{
|
||||
m_qtWindow["parentElement"].call<emscripten::val>("removeChild", m_qtWindow);
|
||||
@ -552,6 +209,8 @@ void QWasmWindow::setGeometry(const QRect &rect)
|
||||
screenGeometry.y() + margins.top()));
|
||||
return result;
|
||||
})();
|
||||
m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());
|
||||
|
||||
const auto frameRect =
|
||||
clientAreaRect
|
||||
.adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
|
||||
@ -604,12 +263,13 @@ bool QWasmWindow::isVisible() const
|
||||
|
||||
QMargins QWasmWindow::frameMargins() const
|
||||
{
|
||||
const auto border = borderMargins();
|
||||
const auto titleBarBounds =
|
||||
QRectF::fromDOMRect(m_titleBar.call<emscripten::val>("getBoundingClientRect"));
|
||||
|
||||
return QMarginsF(border.left(), border.top() + titleBarBounds.height(), border.right(),
|
||||
border.bottom())
|
||||
const auto frameRect =
|
||||
QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect"));
|
||||
const auto canvasRect =
|
||||
QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect"));
|
||||
return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(),
|
||||
frameRect.right() - canvasRect.right(),
|
||||
frameRect.bottom() - canvasRect.bottom())
|
||||
.toMargins();
|
||||
}
|
||||
|
||||
@ -646,44 +306,6 @@ bool QWasmWindow::startSystemResize(Qt::Edges)
|
||||
return false;
|
||||
}
|
||||
|
||||
void QWasmWindow::onRestoreClicked()
|
||||
{
|
||||
window()->setWindowState(Qt::WindowNoState);
|
||||
}
|
||||
|
||||
void QWasmWindow::onMaximizeClicked()
|
||||
{
|
||||
window()->setWindowState(Qt::WindowMaximized);
|
||||
}
|
||||
|
||||
void QWasmWindow::onCloseClicked()
|
||||
{
|
||||
window()->close();
|
||||
}
|
||||
|
||||
void QWasmWindow::onInteraction()
|
||||
{
|
||||
if (!isActive())
|
||||
requestActivateWindow();
|
||||
}
|
||||
|
||||
QMarginsF QWasmWindow::borderMargins() const
|
||||
{
|
||||
const auto frameRect =
|
||||
QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect"));
|
||||
const auto canvasRect =
|
||||
QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect"));
|
||||
return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(),
|
||||
frameRect.right() - canvasRect.right(),
|
||||
frameRect.bottom() - canvasRect.bottom());
|
||||
}
|
||||
|
||||
bool QWasmWindow::isPointOnTitle(QPoint globalPoint) const
|
||||
{
|
||||
return QRectF::fromDOMRect(m_titleBar.call<emscripten::val>("getBoundingClientRect"))
|
||||
.contains(globalPoint);
|
||||
}
|
||||
|
||||
void QWasmWindow::invalidate()
|
||||
{
|
||||
m_compositor->requestUpdateWindow(this);
|
||||
@ -691,13 +313,13 @@ void QWasmWindow::invalidate()
|
||||
|
||||
void QWasmWindow::onActivationChanged(bool active)
|
||||
{
|
||||
syncCSSClassWith(m_qtWindow, "inactive", !active);
|
||||
dom::syncCSSClassWith(m_qtWindow, "inactive", !active);
|
||||
}
|
||||
|
||||
void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
|
||||
{
|
||||
m_flags = flags;
|
||||
syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
|
||||
dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
|
||||
}
|
||||
|
||||
void QWasmWindow::setWindowState(Qt::WindowStates newState)
|
||||
@ -722,7 +344,7 @@ void QWasmWindow::setWindowState(Qt::WindowStates newState)
|
||||
|
||||
void QWasmWindow::setWindowTitle(const QString &title)
|
||||
{
|
||||
m_label.set("innerText", emscripten::val(title.toStdString()));
|
||||
m_nonClientArea->titleBar()->setTitle(title);
|
||||
}
|
||||
|
||||
void QWasmWindow::setWindowIcon(const QIcon &icon)
|
||||
@ -730,14 +352,15 @@ void QWasmWindow::setWindowIcon(const QIcon &icon)
|
||||
const auto dpi = screen()->devicePixelRatio();
|
||||
auto pixmap = icon.pixmap(10 * dpi, 10 * dpi);
|
||||
if (pixmap.isNull()) {
|
||||
m_icon->setImage(IconType::QtLogo);
|
||||
m_nonClientArea->titleBar()->setIcon(
|
||||
Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bytes;
|
||||
QBuffer buffer(&bytes);
|
||||
pixmap.save(&buffer, "png");
|
||||
m_icon->setImage(bytes.toBase64().toStdString(), "png");
|
||||
m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(), "png");
|
||||
}
|
||||
|
||||
void QWasmWindow::applyWindowState()
|
||||
@ -753,11 +376,11 @@ void QWasmWindow::applyWindowState()
|
||||
else
|
||||
newGeom = normalGeometry();
|
||||
|
||||
syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
|
||||
syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
|
||||
dom::syncCSSClassWith(m_qtWindow, "has-title-bar", hasTitleBar());
|
||||
dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
|
||||
|
||||
m_restore->setVisible(isMaximized);
|
||||
m_maximize->setVisible(!isMaximized);
|
||||
m_nonClientArea->titleBar()->setRestoreVisible(isMaximized);
|
||||
m_nonClientArea->titleBar()->setMaximizeVisible(!isMaximized);
|
||||
|
||||
if (isVisible())
|
||||
QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "qwasmbackingstore.h"
|
||||
#include "qwasmscreen.h"
|
||||
#include "qwasmcompositor.h"
|
||||
#include "qwasmwindownonclientarea.h"
|
||||
|
||||
#include <QtCore/private/qstdweb_p.h>
|
||||
#include "QtGui/qopenglcontext.h"
|
||||
@ -24,21 +25,26 @@ class QWasmWindow final : public QPlatformWindow
|
||||
public:
|
||||
QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore);
|
||||
~QWasmWindow() final;
|
||||
|
||||
void destroy();
|
||||
|
||||
void initialize() override;
|
||||
|
||||
void paint();
|
||||
void setZOrder(int order);
|
||||
void onActivationChanged(bool active);
|
||||
bool isVisible() const;
|
||||
|
||||
void onNonClientAreaInteraction();
|
||||
void onRestoreClicked();
|
||||
void onMaximizeClicked();
|
||||
void onToggleMaximized();
|
||||
void onCloseClicked();
|
||||
bool onNonClientEvent(const PointerEvent &event);
|
||||
|
||||
// QPlatformWindow:
|
||||
void initialize() override;
|
||||
void setGeometry(const QRect &) override;
|
||||
void setVisible(bool visible) override;
|
||||
bool isVisible() const;
|
||||
QMargins frameMargins() const override;
|
||||
|
||||
WId winId() const override;
|
||||
|
||||
void propagateSizeHints() override;
|
||||
void raise() override;
|
||||
void lower() override;
|
||||
@ -46,25 +52,20 @@ public:
|
||||
qreal devicePixelRatio() const override;
|
||||
void requestUpdate() override;
|
||||
void requestActivateWindow() override;
|
||||
void setWindowFlags(Qt::WindowFlags flags) override;
|
||||
void setWindowState(Qt::WindowStates state) override;
|
||||
void setWindowTitle(const QString &title) override;
|
||||
void setWindowIcon(const QIcon &icon) override;
|
||||
bool setKeyboardGrabEnabled(bool) override { return false; }
|
||||
bool setMouseGrabEnabled(bool grab) final;
|
||||
bool windowEvent(QEvent *event) final;
|
||||
bool startSystemResize(Qt::Edges edges) final;
|
||||
|
||||
QWasmScreen *platformScreen() const;
|
||||
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
|
||||
QWasmBackingStore *backingStore() const { return m_backingStore; }
|
||||
QWindow *window() const { return m_window; }
|
||||
|
||||
bool startSystemResize(Qt::Edges edges) final;
|
||||
|
||||
bool isPointOnTitle(QPoint point) const;
|
||||
|
||||
void setWindowFlags(Qt::WindowFlags flags) override;
|
||||
void setWindowState(Qt::WindowStates state) override;
|
||||
void setWindowTitle(const QString &title) override;
|
||||
void setWindowIcon(const QIcon &icon) override;
|
||||
void applyWindowState();
|
||||
bool setKeyboardGrabEnabled(bool) override { return false; }
|
||||
bool setMouseGrabEnabled(bool grab) final;
|
||||
bool windowEvent(QEvent *event) final;
|
||||
|
||||
std::string canvasSelector() const;
|
||||
emscripten::val context2d() { return m_context2d; }
|
||||
emscripten::val a11yContainer() { return m_a11yContainer; }
|
||||
@ -73,18 +74,9 @@ public:
|
||||
private:
|
||||
friend class QWasmScreen;
|
||||
|
||||
class Resizer;
|
||||
class WebImageButton;
|
||||
|
||||
QMarginsF borderMargins() const;
|
||||
|
||||
void onRestoreClicked();
|
||||
void onMaximizeClicked();
|
||||
void onCloseClicked();
|
||||
void onInteraction();
|
||||
|
||||
void invalidate();
|
||||
bool hasTitleBar() const;
|
||||
void applyWindowState();
|
||||
|
||||
QWindow *m_window = nullptr;
|
||||
QWasmCompositor *m_compositor = nullptr;
|
||||
@ -94,19 +86,12 @@ private:
|
||||
emscripten::val m_document;
|
||||
emscripten::val m_qtWindow;
|
||||
emscripten::val m_windowContents;
|
||||
emscripten::val m_titleBar;
|
||||
emscripten::val m_label;
|
||||
emscripten::val m_canvasContainer;
|
||||
emscripten::val m_a11yContainer;
|
||||
emscripten::val m_canvas;
|
||||
emscripten::val m_context2d = emscripten::val::undefined();
|
||||
|
||||
std::unique_ptr<Resizer> m_resizer;
|
||||
|
||||
std::unique_ptr<WebImageButton> m_close;
|
||||
std::unique_ptr<WebImageButton> m_maximize;
|
||||
std::unique_ptr<WebImageButton> m_restore;
|
||||
std::unique_ptr<WebImageButton> m_icon;
|
||||
std::unique_ptr<NonClientArea> m_nonClientArea;
|
||||
|
||||
Qt::WindowStates m_state = Qt::WindowNoState;
|
||||
Qt::WindowStates m_previousWindowState = Qt::WindowNoState;
|
||||
|
436
src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
Normal file
436
src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
Normal file
@ -0,0 +1,436 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "qwasmwindownonclientarea.h"
|
||||
|
||||
#include "qwasmbase64iconstore.h"
|
||||
#include "qwasmdom.h"
|
||||
#include "qwasmevent.h"
|
||||
#include "qwasmintegration.h"
|
||||
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
|
||||
#include <QtCore/qassert.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
WebImageButton::Callbacks::Callbacks() = default;
|
||||
WebImageButton::Callbacks::Callbacks(std::function<void()> onInteraction,
|
||||
std::function<void()> onClick)
|
||||
: m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick))
|
||||
{
|
||||
Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO,
|
||||
"Both callbacks need to be either null or non-null");
|
||||
}
|
||||
WebImageButton::Callbacks::~Callbacks() = default;
|
||||
|
||||
WebImageButton::Callbacks::Callbacks(Callbacks &&) = default;
|
||||
WebImageButton::Callbacks &WebImageButton::Callbacks::operator=(Callbacks &&) = default;
|
||||
|
||||
void WebImageButton::Callbacks::onInteraction()
|
||||
{
|
||||
return m_onInteraction();
|
||||
}
|
||||
|
||||
void WebImageButton::Callbacks::onClick()
|
||||
{
|
||||
return m_onClick();
|
||||
}
|
||||
|
||||
WebImageButton::WebImageButton()
|
||||
: m_containerElement(
|
||||
dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_imgElement(dom::document().call<emscripten::val>("createElement", emscripten::val("img")))
|
||||
{
|
||||
m_imgElement.set("draggable", false);
|
||||
|
||||
m_containerElement["classList"].call<void>("add", emscripten::val("image-button"));
|
||||
m_containerElement.call<void>("appendChild", m_imgElement);
|
||||
}
|
||||
|
||||
WebImageButton::~WebImageButton() = default;
|
||||
|
||||
void WebImageButton::setCallbacks(Callbacks callbacks)
|
||||
{
|
||||
if (callbacks) {
|
||||
if (!m_webClickEventCallback) {
|
||||
m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>(
|
||||
m_containerElement, "mousedown", [this](emscripten::val event) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
m_callbacks.onInteraction();
|
||||
});
|
||||
m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>(
|
||||
m_containerElement, "click",
|
||||
[this](emscripten::val) { m_callbacks.onClick(); });
|
||||
}
|
||||
} else {
|
||||
m_webMouseDownEventCallback.reset();
|
||||
m_webClickEventCallback.reset();
|
||||
}
|
||||
dom::syncCSSClassWith(m_containerElement, "action-button", !!callbacks);
|
||||
m_callbacks = std::move(callbacks);
|
||||
}
|
||||
|
||||
void WebImageButton::setImage(std::string_view imageData, std::string_view format)
|
||||
{
|
||||
m_imgElement.set("src",
|
||||
"data:image/" + std::string(format) + ";base64," + std::string(imageData));
|
||||
}
|
||||
|
||||
void WebImageButton::setVisible(bool visible)
|
||||
{
|
||||
m_containerElement["style"].set("display", visible ? "flex" : "none");
|
||||
}
|
||||
|
||||
Resizer::ResizerElement::ResizerElement(emscripten::val parentElement, Qt::Edges edges,
|
||||
Resizer *resizer)
|
||||
: m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_edges(edges),
|
||||
m_resizer(resizer)
|
||||
{
|
||||
Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null");
|
||||
|
||||
m_element["classList"].call<void>("add", emscripten::val("resize-outline"));
|
||||
m_element["classList"].call<void>("add", emscripten::val(cssClassNameForEdges(edges)));
|
||||
|
||||
parentElement.call<void>("appendChild", m_element);
|
||||
|
||||
m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerdown", [this](emscripten::val event) {
|
||||
if (!onPointerDown(*PointerEvent::fromWeb(event)))
|
||||
return;
|
||||
m_resizer->onInteraction();
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
});
|
||||
m_mouseDragEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointermove", [this](emscripten::val event) {
|
||||
if (onPointerMove(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerup", [this](emscripten::val event) {
|
||||
if (onPointerUp(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Resizer::ResizerElement::~ResizerElement()
|
||||
{
|
||||
m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
|
||||
}
|
||||
|
||||
Resizer::ResizerElement::ResizerElement(ResizerElement &&other) = default;
|
||||
|
||||
bool Resizer::ResizerElement::onPointerDown(const PointerEvent &event)
|
||||
{
|
||||
if (event.pointerType != PointerType::Mouse)
|
||||
return false;
|
||||
|
||||
m_element.call<void>("setPointerCapture", event.pointerId);
|
||||
m_capturedPointerId = event.pointerId;
|
||||
|
||||
m_resizer->startResize(m_edges, event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resizer::ResizerElement::onPointerMove(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
m_resizer->continueResize(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resizer::ResizerElement::onPointerUp(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
m_resizer->finishResize();
|
||||
m_element.call<void>("releasePointerCapture", event.pointerId);
|
||||
m_capturedPointerId = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement)
|
||||
: m_window(window), m_windowElement(parentElement)
|
||||
{
|
||||
Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null");
|
||||
|
||||
constexpr std::array<int, 8> ResizeEdges = { Qt::TopEdge | Qt::LeftEdge,
|
||||
Qt::TopEdge,
|
||||
Qt::TopEdge | Qt::RightEdge,
|
||||
Qt::LeftEdge,
|
||||
Qt::RightEdge,
|
||||
Qt::BottomEdge | Qt::LeftEdge,
|
||||
Qt::BottomEdge,
|
||||
Qt::BottomEdge | Qt::RightEdge };
|
||||
std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), std::back_inserter(m_elements),
|
||||
[parentElement, this](int edges) {
|
||||
return std::make_unique<ResizerElement>(parentElement,
|
||||
Qt::Edges::fromInt(edges), this);
|
||||
});
|
||||
}
|
||||
|
||||
Resizer::~Resizer() = default;
|
||||
|
||||
void Resizer::onInteraction()
|
||||
{
|
||||
m_window->onNonClientAreaInteraction();
|
||||
}
|
||||
|
||||
void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
|
||||
{
|
||||
Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress");
|
||||
|
||||
m_currentResizeData.reset(new ResizeData{
|
||||
.edges = resizeEdges,
|
||||
.originInScreenCoords = dom::mapPoint(
|
||||
event.currentTarget, m_window->platformScreen()->element(), event.localPoint),
|
||||
});
|
||||
|
||||
const auto *window = m_window->window();
|
||||
m_currentResizeData->minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
|
||||
window->minimumHeight() - window->geometry().height());
|
||||
|
||||
const auto frameRect =
|
||||
QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
|
||||
const auto screenRect = QRectF::fromDOMRect(
|
||||
m_window->platformScreen()->element().call<emscripten::val>("getBoundingClientRect"));
|
||||
|
||||
const int maxGrowTop = frameRect.top() - screenRect.top();
|
||||
|
||||
m_currentResizeData->maxGrow =
|
||||
QPoint(window->maximumWidth() - window->geometry().width(),
|
||||
std::min(resizeEdges & Qt::Edge::TopEdge ? maxGrowTop : INT_MAX,
|
||||
window->maximumHeight() - window->geometry().height()));
|
||||
|
||||
m_currentResizeData->initialBounds = window->geometry();
|
||||
|
||||
// TODO(mikolajboc): Implement system resize
|
||||
// .m_originInScreenCoords = m_systemDragInitData.lastMouseMovePoint,
|
||||
}
|
||||
|
||||
void Resizer::continueResize(const PointerEvent &event)
|
||||
{
|
||||
const auto pointInScreen = dom::mapPoint(
|
||||
event.currentTarget, m_window->platformScreen()->element(), event.localPoint);
|
||||
const auto amount = pointInScreen - m_currentResizeData->originInScreenCoords;
|
||||
const QPoint cappedGrowVector(
|
||||
std::min(m_currentResizeData->maxGrow.x(),
|
||||
std::max(m_currentResizeData->minShrink.x(),
|
||||
(m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x()
|
||||
: (m_currentResizeData->edges & Qt::Edge::RightEdge)
|
||||
? amount.x()
|
||||
: 0)),
|
||||
std::min(m_currentResizeData->maxGrow.y(),
|
||||
std::max(m_currentResizeData->minShrink.y(),
|
||||
(m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y()
|
||||
: (m_currentResizeData->edges & Qt::Edge::BottomEdge)
|
||||
? amount.y()
|
||||
: 0)));
|
||||
|
||||
auto bounds = m_currentResizeData->initialBounds.adjusted(
|
||||
(m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0,
|
||||
(m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0);
|
||||
|
||||
m_window->window()->setGeometry(bounds);
|
||||
}
|
||||
|
||||
void Resizer::finishResize()
|
||||
{
|
||||
Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress");
|
||||
m_currentResizeData.reset();
|
||||
}
|
||||
|
||||
TitleBar::TitleBar(QWasmWindow *window, emscripten::val parentElement)
|
||||
: m_window(window),
|
||||
m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
|
||||
m_label(dom::document().call<emscripten::val>("createElement", emscripten::val("div")))
|
||||
{
|
||||
m_icon = std::make_unique<WebImageButton>();
|
||||
m_icon->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
|
||||
m_element.call<void>("appendChild", m_icon->htmlElement());
|
||||
m_element.set("className", "title-bar");
|
||||
|
||||
auto spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
|
||||
spacer["style"].set("width", "4px");
|
||||
m_element.call<void>("appendChild", spacer);
|
||||
|
||||
m_label.set("className", "window-name");
|
||||
|
||||
m_element.call<void>("appendChild", m_label);
|
||||
|
||||
spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
|
||||
spacer.set("className", "spacer");
|
||||
m_element.call<void>("appendChild", spacer);
|
||||
|
||||
m_restore = std::make_unique<WebImageButton>();
|
||||
m_restore->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Restore),
|
||||
"svg+xml");
|
||||
m_restore->setCallbacks(
|
||||
WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
|
||||
[this]() { m_window->onRestoreClicked(); }));
|
||||
|
||||
m_element.call<void>("appendChild", m_restore->htmlElement());
|
||||
|
||||
m_maximize = std::make_unique<WebImageButton>();
|
||||
m_maximize->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Maximize),
|
||||
"svg+xml");
|
||||
m_maximize->setCallbacks(
|
||||
WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
|
||||
[this]() { m_window->onMaximizeClicked(); }));
|
||||
|
||||
m_element.call<void>("appendChild", m_maximize->htmlElement());
|
||||
|
||||
m_close = std::make_unique<WebImageButton>();
|
||||
m_close->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::X), "svg+xml");
|
||||
m_close->setCallbacks(
|
||||
WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
|
||||
[this]() { m_window->onCloseClicked(); }));
|
||||
|
||||
m_element.call<void>("appendChild", m_close->htmlElement());
|
||||
|
||||
parentElement.call<void>("appendChild", m_element);
|
||||
|
||||
m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerdown", [this](emscripten::val event) {
|
||||
if (!onPointerDown(*PointerEvent::fromWeb(event)))
|
||||
return;
|
||||
m_window->onNonClientAreaInteraction();
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
});
|
||||
m_mouseDragEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointermove", [this](emscripten::val event) {
|
||||
if (onPointerMove(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "pointerup", [this](emscripten::val event) {
|
||||
if (onPointerUp(*PointerEvent::fromWeb(event))) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
m_doubleClickEvent = std::make_unique<qstdweb::EventCallback>(
|
||||
m_element, "dblclick", [this](emscripten::val event) {
|
||||
if (onDoubleClick()) {
|
||||
event.call<void>("preventDefault");
|
||||
event.call<void>("stopPropagation");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TitleBar::~TitleBar()
|
||||
{
|
||||
m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
|
||||
}
|
||||
|
||||
void TitleBar::setTitle(const QString &title)
|
||||
{
|
||||
m_label.set("innerText", emscripten::val(title.toStdString()));
|
||||
}
|
||||
|
||||
void TitleBar::setRestoreVisible(bool visible)
|
||||
{
|
||||
m_restore->setVisible(visible);
|
||||
}
|
||||
|
||||
void TitleBar::setMaximizeVisible(bool visible)
|
||||
{
|
||||
m_maximize->setVisible(visible);
|
||||
}
|
||||
|
||||
void TitleBar::setIcon(std::string_view imageData, std::string_view format)
|
||||
{
|
||||
m_icon->setImage(imageData, format);
|
||||
}
|
||||
|
||||
void TitleBar::setWidth(int width)
|
||||
{
|
||||
m_element["style"].set("width", std::to_string(width) + "px");
|
||||
}
|
||||
|
||||
QRectF TitleBar::geometry() const
|
||||
{
|
||||
return QRectF::fromDOMRect(m_element.call<emscripten::val>("getBoundingClientRect"));
|
||||
}
|
||||
|
||||
bool TitleBar::onPointerDown(const PointerEvent &event)
|
||||
{
|
||||
if (event.pointerType != PointerType::Mouse)
|
||||
return false;
|
||||
|
||||
m_element.call<void>("setPointerCapture", event.pointerId);
|
||||
m_capturedPointerId = event.pointerId;
|
||||
|
||||
const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint);
|
||||
m_lastMovePoint = targetPointClippedToScreen;
|
||||
m_window->onNonClientEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TitleBar::onPointerMove(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
const QPoint targetPointClippedToScreen = clipPointWithScreen(event.localPoint);
|
||||
const QPoint delta = targetPointClippedToScreen - m_lastMovePoint;
|
||||
m_lastMovePoint = targetPointClippedToScreen;
|
||||
|
||||
m_window->window()->setPosition(m_window->window()->position() + delta);
|
||||
m_window->onNonClientEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TitleBar::onPointerUp(const PointerEvent &event)
|
||||
{
|
||||
if (m_capturedPointerId != event.pointerId)
|
||||
return false;
|
||||
|
||||
m_element.call<void>("releasePointerCapture", event.pointerId);
|
||||
m_capturedPointerId = -1;
|
||||
m_window->onNonClientEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TitleBar::onDoubleClick()
|
||||
{
|
||||
m_window->onToggleMaximized();
|
||||
return true;
|
||||
}
|
||||
|
||||
QPoint TitleBar::clipPointWithScreen(const QPoint &pointInTitleBarCoords) const
|
||||
{
|
||||
auto *screen = m_window->platformScreen();
|
||||
return screen->clipPoint(screen->mapFromLocal(
|
||||
dom::mapPoint(m_element, screen->element(), pointInTitleBarCoords)));
|
||||
}
|
||||
|
||||
NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement)
|
||||
{
|
||||
m_titleBar = std::make_unique<TitleBar>(window, qtWindowElement);
|
||||
m_resizer = std::make_unique<Resizer>(window, qtWindowElement);
|
||||
}
|
||||
|
||||
NonClientArea::~NonClientArea() = default;
|
||||
|
||||
void NonClientArea::onClientAreaWidthChange(int width)
|
||||
{
|
||||
m_titleBar->setWidth(width);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
212
src/plugins/platforms/wasm/qwasmwindownonclientarea.h
Normal file
212
src/plugins/platforms/wasm/qwasmwindownonclientarea.h
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef QWASMWINDOWNONCLIENTAREA_H
|
||||
#define QWASMWINDOWNONCLIENTAREA_H
|
||||
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
#include <QtCore/qnamespace.h>
|
||||
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace qstdweb {
|
||||
class EventCallback;
|
||||
}
|
||||
|
||||
struct PointerEvent;
|
||||
class QWindow;
|
||||
class Resizer;
|
||||
class TitleBar;
|
||||
class QWasmWindow;
|
||||
|
||||
class NonClientArea
|
||||
{
|
||||
public:
|
||||
NonClientArea(QWasmWindow *window, emscripten::val containerElement);
|
||||
~NonClientArea();
|
||||
|
||||
void onClientAreaWidthChange(int width);
|
||||
TitleBar *titleBar() const { return m_titleBar.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Resizer> m_resizer;
|
||||
std::unique_ptr<TitleBar> m_titleBar;
|
||||
};
|
||||
|
||||
class WebImageButton
|
||||
{
|
||||
public:
|
||||
class Callbacks
|
||||
{
|
||||
public:
|
||||
Callbacks();
|
||||
Callbacks(std::function<void()> onInteraction, std::function<void()> onClick);
|
||||
~Callbacks();
|
||||
|
||||
Callbacks(const Callbacks &) = delete;
|
||||
Callbacks(Callbacks &&);
|
||||
Callbacks &operator=(const Callbacks &) = delete;
|
||||
Callbacks &operator=(Callbacks &&);
|
||||
|
||||
operator bool() const { return !!m_onInteraction; }
|
||||
|
||||
void onInteraction();
|
||||
void onClick();
|
||||
|
||||
private:
|
||||
std::function<void()> m_onInteraction;
|
||||
std::function<void()> m_onClick;
|
||||
};
|
||||
|
||||
WebImageButton();
|
||||
~WebImageButton();
|
||||
|
||||
void setCallbacks(Callbacks callbacks);
|
||||
void setImage(std::string_view imageData, std::string_view format);
|
||||
void setVisible(bool visible);
|
||||
|
||||
emscripten::val htmlElement() const { return m_containerElement; }
|
||||
emscripten::val imageElement() const { return m_imgElement; }
|
||||
|
||||
private:
|
||||
emscripten::val m_containerElement;
|
||||
emscripten::val m_imgElement;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webMouseMoveEventCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webMouseDownEventCallback;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_webClickEventCallback;
|
||||
|
||||
Callbacks m_callbacks;
|
||||
};
|
||||
|
||||
class Resizer
|
||||
{
|
||||
public:
|
||||
class ResizerElement
|
||||
{
|
||||
public:
|
||||
static constexpr const char *cssClassNameForEdges(Qt::Edges edges)
|
||||
{
|
||||
switch (edges) {
|
||||
case Qt::TopEdge | Qt::LeftEdge:;
|
||||
return "nw";
|
||||
case Qt::TopEdge:
|
||||
return "n";
|
||||
case Qt::TopEdge | Qt::RightEdge:
|
||||
return "ne";
|
||||
case Qt::LeftEdge:
|
||||
return "w";
|
||||
case Qt::RightEdge:
|
||||
return "e";
|
||||
case Qt::BottomEdge | Qt::LeftEdge:
|
||||
return "sw";
|
||||
case Qt::BottomEdge:
|
||||
return "s";
|
||||
case Qt::BottomEdge | Qt::RightEdge:
|
||||
return "se";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer);
|
||||
~ResizerElement();
|
||||
ResizerElement(const ResizerElement &other) = delete;
|
||||
ResizerElement(ResizerElement &&other);
|
||||
ResizerElement &operator=(const ResizerElement &other) = delete;
|
||||
ResizerElement &operator=(ResizerElement &&other) = delete;
|
||||
|
||||
bool onPointerDown(const PointerEvent &event);
|
||||
bool onPointerMove(const PointerEvent &event);
|
||||
bool onPointerUp(const PointerEvent &event);
|
||||
|
||||
private:
|
||||
emscripten::val m_element;
|
||||
|
||||
int m_capturedPointerId = -1;
|
||||
|
||||
const Qt::Edges m_edges;
|
||||
|
||||
Resizer *m_resizer;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDragEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
|
||||
};
|
||||
|
||||
using ClickCallback = std::function<void()>;
|
||||
|
||||
Resizer(QWasmWindow *window, emscripten::val parentElement);
|
||||
~Resizer();
|
||||
|
||||
private:
|
||||
void onInteraction();
|
||||
void startResize(Qt::Edges resizeEdges, const PointerEvent &event);
|
||||
void continueResize(const PointerEvent &event);
|
||||
void finishResize();
|
||||
|
||||
struct ResizeData
|
||||
{
|
||||
Qt::Edges edges = Qt::Edges::fromInt(0);
|
||||
QPoint originInScreenCoords;
|
||||
QPoint minShrink;
|
||||
QPoint maxGrow;
|
||||
QRect initialBounds;
|
||||
};
|
||||
std::unique_ptr<ResizeData> m_currentResizeData;
|
||||
|
||||
QWasmWindow *m_window;
|
||||
emscripten::val m_windowElement;
|
||||
std::vector<std::unique_ptr<ResizerElement>> m_elements;
|
||||
};
|
||||
|
||||
class TitleBar
|
||||
{
|
||||
public:
|
||||
TitleBar(QWasmWindow *window, emscripten::val parentElement);
|
||||
~TitleBar();
|
||||
|
||||
void setTitle(const QString &title);
|
||||
void setRestoreVisible(bool visible);
|
||||
void setMaximizeVisible(bool visible);
|
||||
void setIcon(std::string_view imageData, std::string_view format);
|
||||
void setWidth(int width);
|
||||
|
||||
QRectF geometry() const;
|
||||
|
||||
private:
|
||||
bool onPointerDown(const PointerEvent &event);
|
||||
bool onPointerMove(const PointerEvent &event);
|
||||
bool onPointerUp(const PointerEvent &event);
|
||||
bool onDoubleClick();
|
||||
|
||||
QPoint clipPointWithScreen(const QPoint &pointInTitleBarCoords) const;
|
||||
|
||||
QWasmWindow *m_window;
|
||||
|
||||
emscripten::val m_element;
|
||||
emscripten::val m_label;
|
||||
|
||||
std::unique_ptr<WebImageButton> m_close;
|
||||
std::unique_ptr<WebImageButton> m_maximize;
|
||||
std::unique_ptr<WebImageButton> m_restore;
|
||||
std::unique_ptr<WebImageButton> m_icon;
|
||||
|
||||
int m_capturedPointerId = -1;
|
||||
QPoint m_lastMovePoint;
|
||||
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseDragEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
|
||||
std::unique_ptr<qstdweb::EventCallback> m_doubleClickEvent;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
#endif // QWASMWINDOWNONCLIENTAREA_H
|
Loading…
x
Reference in New Issue
Block a user