Deliver QTabletEvents to pointer handlers
At this time, there are not yet any specialized handlers to do anything specifically with tablet events; but we demonstrate how to use HoverHandler to detect the type of stylus in use, and how to use PointHandler to draw on a Canvas. Unfortunately, events of types TabletEnterProximity and TabletLeaveProximity are not delivered to the window, only to QGuiApplication. So HoverHandler can detect when the stylus is moved out of its parent Item (as long as it's still hovering over the tablet surface), but cannot detect when the stylus leaves the tablet completely. In Qt 5 that would require a custom application subclass (see qtbase/examples/widgets/widgets/tablet/tabletapplication.cpp). Fixes: QTBUG-79660 Change-Id: I81fdb99082dc41c0455085e6b6d3952402bf8742 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
parent
c48b0902b5
commit
8e822e981d
|
@ -120,7 +120,10 @@ void QQuickHandlerPoint::reset(const QQuickEventPoint *point)
|
|||
m_pressure = tp->pressure();
|
||||
m_ellipseDiameters = tp->ellipseDiameters();
|
||||
} else if (event->asPointerTabletEvent()) {
|
||||
// TODO
|
||||
m_uniqueId = event->device()->uniqueId();
|
||||
m_rotation = static_cast<const QQuickEventTabletPoint *>(point)->rotation();
|
||||
m_pressure = static_cast<const QQuickEventTabletPoint *>(point)->pressure();
|
||||
m_ellipseDiameters = QSizeF();
|
||||
} else {
|
||||
m_uniqueId = event->device()->uniqueId();
|
||||
m_rotation = 0;
|
||||
|
|
|
@ -93,11 +93,22 @@ bool QQuickHoverHandler::wantsPointerEvent(QQuickPointerEvent *event)
|
|||
{
|
||||
QQuickEventPoint *point = event->point(0);
|
||||
if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) {
|
||||
// assume this is a mouse event, so there's only one point
|
||||
// assume this is a mouse or tablet event, so there's only one point
|
||||
setPointId(point->pointId());
|
||||
return true;
|
||||
}
|
||||
setHovered(false);
|
||||
|
||||
// Some hover events come from QQuickWindow::tabletEvent(). In between,
|
||||
// some hover events come from QQWindowPrivate::flushFrameSynchronousEvents(),
|
||||
// but those look like mouse events. If a particular HoverHandler instance
|
||||
// is filtering for tablet events only (e.g. by setting
|
||||
// acceptedDevices:PointerDevice.Stylus), those events should not cause
|
||||
// the hovered property to transition to false prematurely.
|
||||
// If a QQuickPointerTabletEvent caused the hovered property to become true,
|
||||
// then only another QQuickPointerTabletEvent can make it become false.
|
||||
if (!(m_hoveredTablet && event->asPointerMouseEvent()))
|
||||
setHovered(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -107,6 +118,8 @@ void QQuickHoverHandler::handleEventPoint(QQuickEventPoint *point)
|
|||
if (point->state() == QQuickEventPoint::Released &&
|
||||
point->pointerEvent()->device()->pointerType() == QQuickPointerDevice::Finger)
|
||||
hovered = false;
|
||||
else if (point->pointerEvent()->asPointerTabletEvent())
|
||||
m_hoveredTablet = true;
|
||||
setHovered(hovered);
|
||||
setPassiveGrab(point);
|
||||
}
|
||||
|
@ -124,6 +137,8 @@ void QQuickHoverHandler::setHovered(bool hovered)
|
|||
if (m_hovered != hovered) {
|
||||
qCDebug(lcHoverHandler) << objectName() << "hovered" << m_hovered << "->" << hovered;
|
||||
m_hovered = hovered;
|
||||
if (!hovered)
|
||||
m_hoveredTablet = false;
|
||||
emit hoveredChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ private:
|
|||
|
||||
private:
|
||||
bool m_hovered = false;
|
||||
bool m_hoveredTablet = false;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -658,14 +658,73 @@ QQuickPointerDevice *QQuickPointerDevice::genericMouseDevice()
|
|||
return g_genericMouseDevice;
|
||||
}
|
||||
|
||||
QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id)
|
||||
QQuickPointerDevice *QQuickPointerDevice::tabletDevice(const QTabletEvent *event)
|
||||
{
|
||||
auto it = g_tabletDevices->find(id);
|
||||
// QTabletEvent::uniqueId() is the same for the pointy end and the eraser end of the stylus.
|
||||
// We need to make those unique. QTabletEvent::PointerType only needs 2 bits' worth of storage.
|
||||
// The key into g_tabletDevices just needs to be unique; we don't need to extract uniqueId
|
||||
// back out of it, because QQuickPointerDevice stores that separately anyway.
|
||||
// So the shift-and-add can be thought of as a sort of hash function, even though
|
||||
// most of the time the result will be recognizable because the uniqueId MSBs are often 0.
|
||||
qint64 key = event->uniqueId() + (qint64(event->pointerType()) << 60);
|
||||
auto it = g_tabletDevices->find(key);
|
||||
if (it != g_tabletDevices->end())
|
||||
return it.value();
|
||||
|
||||
// ### Figure out how to populate the tablet devices
|
||||
return nullptr;
|
||||
DeviceType type = UnknownDevice;
|
||||
int buttonCount = 0;
|
||||
Capabilities caps = Position | Pressure | Hover;
|
||||
// TODO Qt 6: we can't know for sure about XTilt or YTilt until we have a
|
||||
// QTabletDevice populated with capabilities provided by QPA plugins
|
||||
|
||||
switch (event->device()) {
|
||||
case QTabletEvent::Stylus:
|
||||
type = QQuickPointerDevice::Stylus;
|
||||
buttonCount = 3;
|
||||
break;
|
||||
case QTabletEvent::RotationStylus:
|
||||
type = QQuickPointerDevice::Stylus;
|
||||
caps |= QQuickPointerDevice::Rotation;
|
||||
buttonCount = 1;
|
||||
break;
|
||||
case QTabletEvent::Airbrush:
|
||||
type = QQuickPointerDevice::Airbrush;
|
||||
buttonCount = 2;
|
||||
break;
|
||||
case QTabletEvent::Puck:
|
||||
type = QQuickPointerDevice::Puck;
|
||||
buttonCount = 3;
|
||||
break;
|
||||
case QTabletEvent::FourDMouse:
|
||||
type = QQuickPointerDevice::Mouse;
|
||||
caps |= QQuickPointerDevice::Rotation;
|
||||
buttonCount = 3;
|
||||
break;
|
||||
default:
|
||||
type = QQuickPointerDevice::UnknownDevice;
|
||||
break;
|
||||
}
|
||||
|
||||
PointerType ptype = GenericPointer;
|
||||
switch (event->pointerType()) {
|
||||
case QTabletEvent::Pen:
|
||||
ptype = Pen;
|
||||
break;
|
||||
case QTabletEvent::Eraser:
|
||||
ptype = Eraser;
|
||||
break;
|
||||
case QTabletEvent::Cursor:
|
||||
ptype = Cursor;
|
||||
break;
|
||||
case QTabletEvent::UnknownPointer:
|
||||
break;
|
||||
}
|
||||
|
||||
QQuickPointerDevice *device = new QQuickPointerDevice(type, ptype, caps, 1, buttonCount,
|
||||
QLatin1String("tablet tool ") + QString::number(event->uniqueId()), event->uniqueId());
|
||||
|
||||
g_tabletDevices->insert(key, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1284,6 +1343,12 @@ QVector2D QQuickEventPoint::estimatedVelocity() const
|
|||
QQuickPointerEvent::~QQuickPointerEvent()
|
||||
{}
|
||||
|
||||
QQuickPointerMouseEvent::QQuickPointerMouseEvent(QObject *parent, QQuickPointerDevice *device)
|
||||
: QQuickSinglePointEvent(parent, device)
|
||||
{
|
||||
m_point = new QQuickEventPoint(this);
|
||||
}
|
||||
|
||||
QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event)
|
||||
{
|
||||
auto ev = static_cast<QMouseEvent*>(event);
|
||||
|
@ -1398,6 +1463,12 @@ void QQuickPointerTouchEvent::localize(QQuickItem *target)
|
|||
}
|
||||
|
||||
#if QT_CONFIG(gestures)
|
||||
QQuickPointerNativeGestureEvent::QQuickPointerNativeGestureEvent(QObject *parent, QQuickPointerDevice *device)
|
||||
: QQuickSinglePointEvent(parent, device)
|
||||
{
|
||||
m_point = new QQuickEventPoint(this);
|
||||
}
|
||||
|
||||
QQuickPointerEvent *QQuickPointerNativeGestureEvent::reset(QEvent *event)
|
||||
{
|
||||
auto ev = static_cast<QNativeGestureEvent*>(event);
|
||||
|
@ -1560,6 +1631,12 @@ QQuickEventPoint *QQuickSinglePointEvent::point(int i) const
|
|||
\note Many platforms provide no such information. On such platforms,
|
||||
\c inverted always returns false.
|
||||
*/
|
||||
QQuickPointerScrollEvent::QQuickPointerScrollEvent(QObject *parent, QQuickPointerDevice *device)
|
||||
: QQuickSinglePointEvent(parent, device)
|
||||
{
|
||||
m_point = new QQuickEventPoint(this);
|
||||
}
|
||||
|
||||
QQuickPointerEvent *QQuickPointerScrollEvent::reset(QEvent *event)
|
||||
{
|
||||
m_event = static_cast<QInputEvent*>(event);
|
||||
|
@ -1832,6 +1909,81 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte
|
|||
return &m_synthMouseEvent;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(tabletevent)
|
||||
QQuickPointerTabletEvent::QQuickPointerTabletEvent(QObject *parent, QQuickPointerDevice *device)
|
||||
: QQuickSinglePointEvent(parent, device)
|
||||
{
|
||||
m_point = new QQuickEventTabletPoint(this);
|
||||
}
|
||||
|
||||
QQuickPointerEvent *QQuickPointerTabletEvent::reset(QEvent *event)
|
||||
{
|
||||
auto ev = static_cast<QTabletEvent*>(event);
|
||||
m_event = ev;
|
||||
if (!event)
|
||||
return this;
|
||||
|
||||
Q_ASSERT(m_device == QQuickPointerDevice::tabletDevice(ev));
|
||||
m_device->eventDeliveryTargets().clear();
|
||||
m_button = ev->button();
|
||||
m_pressedButtons = ev->buttons();
|
||||
static_cast<QQuickEventTabletPoint *>(m_point)->reset(ev);
|
||||
return this;
|
||||
}
|
||||
|
||||
QQuickEventTabletPoint::QQuickEventTabletPoint(QQuickPointerTabletEvent *parent)
|
||||
: QQuickEventPoint(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void QQuickEventTabletPoint::reset(const QTabletEvent *ev)
|
||||
{
|
||||
Qt::TouchPointState state = Qt::TouchPointStationary;
|
||||
switch (ev->type()) {
|
||||
case QEvent::TabletPress:
|
||||
state = Qt::TouchPointPressed;
|
||||
clearPassiveGrabbers();
|
||||
break;
|
||||
case QEvent::TabletRelease:
|
||||
state = Qt::TouchPointReleased;
|
||||
break;
|
||||
case QEvent::TabletMove:
|
||||
state = Qt::TouchPointMoved;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QQuickEventPoint::reset(state, ev->posF(), 1, ev->timestamp());
|
||||
m_rotation = ev->rotation();
|
||||
m_pressure = ev->pressure();
|
||||
m_tangentialPressure = ev->tangentialPressure();
|
||||
m_tilt = QVector2D(ev->xTilt(), ev->yTilt());
|
||||
}
|
||||
|
||||
bool QQuickPointerTabletEvent::isPressEvent() const
|
||||
{
|
||||
auto me = static_cast<QTabletEvent *>(m_event);
|
||||
return me->type() == QEvent::TabletPress;
|
||||
}
|
||||
|
||||
bool QQuickPointerTabletEvent::isUpdateEvent() const
|
||||
{
|
||||
auto me = static_cast<QTabletEvent *>(m_event);
|
||||
return me->type() == QEvent::TabletMove;
|
||||
}
|
||||
|
||||
bool QQuickPointerTabletEvent::isReleaseEvent() const
|
||||
{
|
||||
auto me = static_cast<QTabletEvent *>(m_event);
|
||||
return me->type() == QEvent::TabletRelease;
|
||||
}
|
||||
|
||||
QTabletEvent *QQuickPointerTabletEvent::asTabletEvent() const
|
||||
{
|
||||
return static_cast<QTabletEvent *>(m_event);
|
||||
}
|
||||
#endif // QT_CONFIG(tabletevent)
|
||||
|
||||
#if QT_CONFIG(gestures)
|
||||
bool QQuickPointerNativeGestureEvent::isPressEvent() const
|
||||
{
|
||||
|
|
|
@ -476,8 +476,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointEvent : public QQuickPointerEvent
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QQuickSinglePointEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr)
|
||||
: QQuickPointerEvent(parent, device), m_point(new QQuickEventPoint(this)) { }
|
||||
QQuickSinglePointEvent(QObject *parent, QQuickPointerDevice *device)
|
||||
: QQuickPointerEvent(parent, device) { }
|
||||
|
||||
void localize(QQuickItem *target) override;
|
||||
int pointCount() const override { return 1; }
|
||||
|
@ -491,7 +491,7 @@ public:
|
|||
bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override;
|
||||
|
||||
protected:
|
||||
QQuickEventPoint *m_point;
|
||||
QQuickEventPoint *m_point = nullptr;
|
||||
|
||||
Q_DISABLE_COPY(QQuickSinglePointEvent)
|
||||
};
|
||||
|
@ -505,8 +505,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerMouseEvent : public QQuickSinglePointE
|
|||
QML_ADDED_IN_MINOR_VERSION(12)
|
||||
|
||||
public:
|
||||
QQuickPointerMouseEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr)
|
||||
: QQuickSinglePointEvent(parent, device) { }
|
||||
QQuickPointerMouseEvent(QObject *parent, QQuickPointerDevice *device);
|
||||
|
||||
QQuickPointerEvent *reset(QEvent *) override;
|
||||
bool isPressEvent() const override;
|
||||
|
@ -568,6 +567,60 @@ private:
|
|||
Q_DISABLE_COPY(QQuickPointerTouchEvent)
|
||||
};
|
||||
|
||||
#if QT_CONFIG(tabletevent)
|
||||
class Q_QUICK_PRIVATE_EXPORT QQuickEventTabletPoint : public QQuickEventPoint
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(qreal rotation READ rotation)
|
||||
Q_PROPERTY(qreal pressure READ pressure)
|
||||
Q_PROPERTY(qreal tangentialPressure READ tangentialPressure)
|
||||
Q_PROPERTY(QVector2D tilt READ tilt)
|
||||
|
||||
QML_NAMED_ELEMENT(EventTabletPoint)
|
||||
QML_UNCREATABLE("EventTouchPoint is only available as a member of PointerEvent.")
|
||||
QML_ADDED_IN_MINOR_VERSION(15)
|
||||
|
||||
public:
|
||||
QQuickEventTabletPoint(QQuickPointerTabletEvent *parent);
|
||||
|
||||
void reset(const QTabletEvent *e);
|
||||
|
||||
qreal rotation() const { return m_rotation; }
|
||||
qreal pressure() const { return m_pressure; }
|
||||
qreal tangentialPressure() const { return m_tangentialPressure; }
|
||||
QVector2D tilt() const { return m_tilt; }
|
||||
|
||||
private:
|
||||
qreal m_rotation;
|
||||
qreal m_pressure;
|
||||
qreal m_tangentialPressure;
|
||||
QVector2D m_tilt;
|
||||
|
||||
friend class QQuickPointerTouchEvent;
|
||||
|
||||
Q_DISABLE_COPY(QQuickEventTabletPoint)
|
||||
};
|
||||
|
||||
class Q_QUICK_PRIVATE_EXPORT QQuickPointerTabletEvent : public QQuickSinglePointEvent
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QQuickPointerTabletEvent(QObject *parent, QQuickPointerDevice *device);
|
||||
|
||||
QQuickPointerEvent *reset(QEvent *) override;
|
||||
bool isPressEvent() const override;
|
||||
bool isUpdateEvent() const override;
|
||||
bool isReleaseEvent() const override;
|
||||
QQuickPointerTabletEvent *asPointerTabletEvent() override { return this; }
|
||||
const QQuickPointerTabletEvent *asPointerTabletEvent() const override { return this; }
|
||||
const QQuickEventTabletPoint *tabletPoint() const { return static_cast<QQuickEventTabletPoint *>(m_point); }
|
||||
|
||||
QTabletEvent *asTabletEvent() const;
|
||||
|
||||
Q_DISABLE_COPY(QQuickPointerTabletEvent)
|
||||
};
|
||||
#endif // QT_CONFIG(tabletevent)
|
||||
|
||||
#if QT_CONFIG(gestures)
|
||||
class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickSinglePointEvent
|
||||
{
|
||||
|
@ -576,8 +629,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickSing
|
|||
Q_PROPERTY(qreal value READ value CONSTANT)
|
||||
|
||||
public:
|
||||
QQuickPointerNativeGestureEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr)
|
||||
: QQuickSinglePointEvent(parent, device) { }
|
||||
QQuickPointerNativeGestureEvent(QObject *parent, QQuickPointerDevice *device);
|
||||
|
||||
QQuickPointerEvent *reset(QEvent *) override;
|
||||
bool isPressEvent() const override;
|
||||
|
@ -606,8 +658,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerScrollEvent : public QQuickSinglePoint
|
|||
QML_ADDED_IN_MINOR_VERSION(14)
|
||||
|
||||
public:
|
||||
QQuickPointerScrollEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr)
|
||||
: QQuickSinglePointEvent(parent, device) { }
|
||||
QQuickPointerScrollEvent(QObject *parent, QQuickPointerDevice *device);
|
||||
|
||||
QQuickPointerEvent *reset(QEvent *) override;
|
||||
void localize(QQuickItem *target) override;
|
||||
|
@ -712,7 +763,7 @@ public:
|
|||
static QQuickPointerDevice *touchDevice(const QTouchDevice *d);
|
||||
static QList<QQuickPointerDevice *> touchDevices();
|
||||
static QQuickPointerDevice *genericMouseDevice();
|
||||
static QQuickPointerDevice *tabletDevice(qint64);
|
||||
static QQuickPointerDevice *tabletDevice(const QTabletEvent *event);
|
||||
|
||||
QVector<QQuickPointerHandler *> &eventDeliveryTargets() { return m_eventDeliveryTargets; }
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ Q_LOGGING_CATEGORY(DBG_TOUCH, "qt.quick.touch")
|
|||
Q_LOGGING_CATEGORY(DBG_TOUCH_TARGET, "qt.quick.touch.target")
|
||||
Q_LOGGING_CATEGORY(DBG_MOUSE, "qt.quick.mouse")
|
||||
Q_LOGGING_CATEGORY(DBG_MOUSE_TARGET, "qt.quick.mouse.target")
|
||||
Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet")
|
||||
Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target")
|
||||
Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target")
|
||||
Q_LOGGING_CATEGORY(DBG_HOVER_TRACE, "qt.quick.hover.trace")
|
||||
|
@ -2122,6 +2123,17 @@ void QQuickWindow::wheelEvent(QWheelEvent *event)
|
|||
}
|
||||
#endif // wheelevent
|
||||
|
||||
#if QT_CONFIG(tabletevent)
|
||||
/*! \reimp */
|
||||
void QQuickWindow::tabletEvent(QTabletEvent *event)
|
||||
{
|
||||
Q_D(QQuickWindow);
|
||||
qCDebug(lcTablet) << event;
|
||||
// TODO Qt 6: make sure TabletEnterProximity and TabletLeaveProximity are delivered here
|
||||
d->deliverPointerEvent(d->pointerEventInstance(event));
|
||||
}
|
||||
#endif // tabletevent
|
||||
|
||||
bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event)
|
||||
{
|
||||
qCDebug(DBG_TOUCH) << event;
|
||||
|
@ -2400,8 +2412,12 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevic
|
|||
#endif
|
||||
ev = new QQuickPointerTouchEvent(q, device);
|
||||
break;
|
||||
case QQuickPointerDevice::Stylus:
|
||||
case QQuickPointerDevice::Airbrush:
|
||||
case QQuickPointerDevice::Puck:
|
||||
ev = new QQuickPointerTabletEvent(q, device);
|
||||
break;
|
||||
default:
|
||||
// TODO tablet event types
|
||||
break;
|
||||
}
|
||||
pointerEventInstances << ev;
|
||||
|
@ -2432,7 +2448,15 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QEvent *event) con
|
|||
case QEvent::TouchCancel:
|
||||
dev = QQuickPointerDevice::touchDevice(static_cast<QTouchEvent *>(event)->device());
|
||||
break;
|
||||
// TODO tablet event types
|
||||
#if QT_CONFIG(tabletevent)
|
||||
case QEvent::TabletPress:
|
||||
case QEvent::TabletMove:
|
||||
case QEvent::TabletRelease:
|
||||
case QEvent::TabletEnterProximity:
|
||||
case QEvent::TabletLeaveProximity:
|
||||
dev = QQuickPointerDevice::tabletDevice(static_cast<QTabletEvent *>(event));
|
||||
break;
|
||||
#endif
|
||||
#if QT_CONFIG(gestures)
|
||||
case QEvent::NativeGesture:
|
||||
dev = QQuickPointerDevice::touchDevice(static_cast<QNativeGestureEvent *>(event)->device());
|
||||
|
@ -2467,6 +2491,11 @@ void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event)
|
|||
deliverTouchEvent(event->asPointerTouchEvent());
|
||||
} else {
|
||||
deliverSinglePointEventUntilAccepted(event);
|
||||
// If any handler got interested in the tablet event, we don't want to receive a synth-mouse event from QtGui
|
||||
// TODO Qt 6: QTabletEvent will be accepted by default, like other events
|
||||
if (event->asPointerTabletEvent() &&
|
||||
(!event->point(0)->passiveGrabbers().isEmpty() || event->point(0)->exclusiveGrabber()))
|
||||
event->setAccepted(true);
|
||||
}
|
||||
|
||||
event->reset(nullptr);
|
||||
|
|
|
@ -252,6 +252,9 @@ protected:
|
|||
#if QT_CONFIG(wheelevent)
|
||||
void wheelEvent(QWheelEvent *) override;
|
||||
#endif
|
||||
#if QT_CONFIG(tabletevent)
|
||||
void tabletEvent(QTabletEvent *) override;
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
void maybeUpdate();
|
||||
|
|
|
@ -55,6 +55,7 @@ private slots:
|
|||
void initTestCase();
|
||||
|
||||
void singleTouch();
|
||||
void tabletStylus();
|
||||
void simultaneousMultiTouch();
|
||||
void pressedMultipleButtons_data();
|
||||
void pressedMultipleButtons();
|
||||
|
@ -136,6 +137,65 @@ void tst_PointHandler::singleTouch()
|
|||
QCOMPARE(translationSpy.count(), 3);
|
||||
}
|
||||
|
||||
void tst_PointHandler::tabletStylus()
|
||||
{
|
||||
qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents, false);
|
||||
QScopedPointer<QQuickView> windowPtr;
|
||||
createView(windowPtr, "pointTracker.qml");
|
||||
QQuickView * window = windowPtr.data();
|
||||
QQuickPointHandler *handler = window->rootObject()->findChild<QQuickPointHandler *>("pointHandler");
|
||||
QVERIFY(handler);
|
||||
handler->setAcceptedDevices(QQuickPointerDevice::Stylus);
|
||||
|
||||
QSignalSpy activeSpy(handler, SIGNAL(activeChanged()));
|
||||
QSignalSpy pointSpy(handler, SIGNAL(pointChanged()));
|
||||
QSignalSpy translationSpy(handler, SIGNAL(translationChanged()));
|
||||
|
||||
QPoint point(100,100);
|
||||
const qint64 stylusId = 1234567890;
|
||||
|
||||
QWindowSystemInterface::handleTabletEvent(window, point, window->mapToGlobal(point),
|
||||
QTabletEvent::Stylus, QTabletEvent::Pen, Qt::LeftButton, 0.5, 25, 35, 0.6, 12.3, 3, stylusId, Qt::NoModifier);
|
||||
QTRY_COMPARE(handler->active(), true);
|
||||
QCOMPARE(activeSpy.count(), 1);
|
||||
QCOMPARE(pointSpy.count(), 1);
|
||||
QCOMPARE(handler->point().position().toPoint(), point);
|
||||
QCOMPARE(handler->point().scenePosition().toPoint(), point);
|
||||
QCOMPARE(handler->point().pressedButtons(), Qt::LeftButton);
|
||||
QCOMPARE(handler->point().pressure(), 0.5);
|
||||
QCOMPARE(handler->point().rotation(), 12.3);
|
||||
QCOMPARE(handler->point().uniqueId().numericId(), stylusId);
|
||||
QCOMPARE(handler->translation(), QVector2D());
|
||||
QCOMPARE(translationSpy.count(), 1);
|
||||
|
||||
point += QPoint(10, 10);
|
||||
QWindowSystemInterface::handleTabletEvent(window, point, window->mapToGlobal(point),
|
||||
QTabletEvent::Stylus, QTabletEvent::Pen, Qt::LeftButton, 0.45, 23, 33, 0.57, 15.6, 3.4, stylusId, Qt::NoModifier);
|
||||
QTRY_COMPARE(pointSpy.count(), 2);
|
||||
QCOMPARE(handler->active(), true);
|
||||
QCOMPARE(activeSpy.count(), 1);
|
||||
QCOMPARE(handler->point().position().toPoint(), point);
|
||||
QCOMPARE(handler->point().scenePosition().toPoint(), point);
|
||||
QCOMPARE(handler->point().pressPosition().toPoint(), QPoint(100, 100));
|
||||
QCOMPARE(handler->point().scenePressPosition().toPoint(), QPoint(100, 100));
|
||||
QCOMPARE(handler->point().pressedButtons(), Qt::LeftButton);
|
||||
QCOMPARE(handler->point().pressure(), 0.45);
|
||||
QCOMPARE(handler->point().rotation(), 15.6);
|
||||
QCOMPARE(handler->point().uniqueId().numericId(), stylusId);
|
||||
QVERIFY(handler->point().velocity().x() > 0);
|
||||
QVERIFY(handler->point().velocity().y() > 0);
|
||||
QCOMPARE(handler->translation(), QVector2D(10, 10));
|
||||
QCOMPARE(translationSpy.count(), 2);
|
||||
|
||||
QWindowSystemInterface::handleTabletEvent(window, point, window->mapToGlobal(point),
|
||||
QTabletEvent::Stylus, QTabletEvent::Pen, Qt::NoButton, 0, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier);
|
||||
QTRY_COMPARE(handler->active(), false);
|
||||
QCOMPARE(activeSpy.count(), 2);
|
||||
QCOMPARE(pointSpy.count(), 3);
|
||||
QCOMPARE(handler->translation(), QVector2D());
|
||||
QCOMPARE(translationSpy.count(), 3);
|
||||
}
|
||||
|
||||
void tst_PointHandler::simultaneousMultiTouch()
|
||||
{
|
||||
QScopedPointer<QQuickView> windowPtr;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the manual tests of the Qt Toolkit.
|
||||
|
@ -57,6 +57,7 @@ Window {
|
|||
addExample("multibuttons", "TapHandler: gesturePolicy (99 red balloons)", Qt.resolvedUrl("multibuttons.qml"))
|
||||
addExample("flickable with Handlers", "Flickable with buttons, sliders etc. implemented in various ways", Qt.resolvedUrl("flickableWithHandlers.qml"))
|
||||
addExample("tap and drag", "Flickable with all possible combinations of TapHandler and DragHandler children", Qt.resolvedUrl("pointerDrag.qml"))
|
||||
addExample("tablet canvas", "PointHandler and HoverHandler with a tablet: detect the stylus, and draw", Qt.resolvedUrl("tabletCanvasDrawing.qml"))
|
||||
}
|
||||
}
|
||||
Item {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<file>pointerDrag.qml</file>
|
||||
<file>singlePointHandlerProperties.qml</file>
|
||||
<file>sidebar.qml</file>
|
||||
<file>tabletCanvasDrawing.qml</file>
|
||||
<file>tapHandler.qml</file>
|
||||
<file>content/CheckBox.qml</file>
|
||||
<file>content/FakeFlickable.qml</file>
|
||||
|
@ -30,6 +31,10 @@
|
|||
<file>content/TouchpointFeedbackSprite.qml</file>
|
||||
<file>resources/arrowhead.png</file>
|
||||
<file>resources/balloon.png</file>
|
||||
<file>resources/cursor-airbrush.png</file>
|
||||
<file>resources/cursor-eraser.png</file>
|
||||
<file>resources/cursor-felt-marker.png</file>
|
||||
<file>resources/cursor-pencil.png</file>
|
||||
<file>resources/fighter.png</file>
|
||||
<file>resources/fingersprite.png</file>
|
||||
<file>resources/grabbing-location.svg</file>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 823 B |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 513 B |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,242 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the manual tests of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** BSD License Usage
|
||||
** Alternatively, you may use this file under the terms of the BSD license
|
||||
** as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import "content"
|
||||
|
||||
Rectangle {
|
||||
width: 1024
|
||||
height: 1024
|
||||
color: "#444"
|
||||
|
||||
ColumnLayout {
|
||||
x: -15; width: 80
|
||||
height: parent.height
|
||||
Slider {
|
||||
id: hueSlider
|
||||
width: 80; height: 200
|
||||
Layout.fillHeight: true
|
||||
label: "hue"
|
||||
Rectangle {
|
||||
color: "beige"
|
||||
width: 30
|
||||
height: 25
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Rectangle {
|
||||
border.color: "white"
|
||||
color: canvas.drawingColor
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
Slider {
|
||||
id: saturationSlider
|
||||
width: 80; height: 200
|
||||
Layout.fillHeight: true
|
||||
label: "sat"
|
||||
}
|
||||
Slider {
|
||||
id: lightnessSlider
|
||||
width: 80; height: 200
|
||||
Layout.fillHeight: true
|
||||
label: "light"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
x: parent.width - 65; width: 80
|
||||
height: parent.height
|
||||
Slider {
|
||||
id: widthSlider
|
||||
width: 80; height: 200
|
||||
Layout.fillHeight: true
|
||||
label: "width"
|
||||
}
|
||||
Slider {
|
||||
id: alphaSlider
|
||||
width: 80; height: 200
|
||||
Layout.fillHeight: true
|
||||
label: "alpha"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
width: 640
|
||||
height: 480
|
||||
color: "beige"
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 10
|
||||
leftMargin: 50
|
||||
rightMargin: 50
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
property color drawingColor: Qt.hsla(hueSlider.value / 100.0,
|
||||
saturationSlider.value / 100.0,
|
||||
lightnessSlider.value / 100.0,
|
||||
alphaSlider.value / 100.0)
|
||||
property var points: []
|
||||
property var pressures: []
|
||||
property var pointerType: PointerDevice.Pen
|
||||
onPaint: {
|
||||
if (points.length < 2)
|
||||
return
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.save()
|
||||
ctx.strokeStyle = pointerType === PointerDevice.Pen ? drawingColor : "beige"
|
||||
ctx.lineCap = "round"
|
||||
if (pressures.length === points.length) {
|
||||
for (var i = 1; i < points.length; i++) {
|
||||
ctx.lineWidth = pressures[i] * widthSlider.value
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(points[i - 1].x, points[i - 1].y)
|
||||
ctx.lineTo(points[i].x, points[i].y)
|
||||
ctx.stroke()
|
||||
}
|
||||
points = points.slice(points.length - 2, 1)
|
||||
pressures = pressures.slice(pressures.length - 2, 1)
|
||||
} else {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(points[0].x, points[0].y)
|
||||
for (var i = 1; i < points.length; i++)
|
||||
ctx.lineTo(points[i].x, points[i].y)
|
||||
ctx.lineWidth = widthSlider
|
||||
ctx.stroke()
|
||||
points = points.slice(points.length - 2, 1)
|
||||
pressures = []
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
PointHandler {
|
||||
acceptedPointerTypes: PointerDevice.Pen
|
||||
onActiveChanged:
|
||||
if (active) {
|
||||
canvas.pointerType = PointerDevice.Pen
|
||||
} else {
|
||||
canvas.points = []
|
||||
canvas.pressures = []
|
||||
}
|
||||
onPointChanged:
|
||||
if (active) {
|
||||
canvas.points.push(point.position)
|
||||
canvas.pressures.push(point.pressure)
|
||||
canvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
PointHandler {
|
||||
acceptedPointerTypes: PointerDevice.Eraser
|
||||
onActiveChanged:
|
||||
if (active) {
|
||||
canvas.pointerType = PointerDevice.Eraser
|
||||
} else {
|
||||
canvas.points = []
|
||||
canvas.pressures = []
|
||||
}
|
||||
onPointChanged:
|
||||
if (active) {
|
||||
canvas.points.push(point.position)
|
||||
canvas.pressures.push(point.pressure)
|
||||
canvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: stylusHandler
|
||||
acceptedDevices: PointerDevice.Stylus
|
||||
acceptedPointerTypes: PointerDevice.Pen
|
||||
target: Image {
|
||||
parent: rect
|
||||
source: stylusHandler.point.rotation === 0 ?
|
||||
"resources/cursor-pencil.png" : "resources/cursor-felt-marker.png"
|
||||
visible: stylusHandler.hovered
|
||||
rotation: stylusHandler.point.rotation
|
||||
x: stylusHandler.point.position.x
|
||||
y: stylusHandler.point.position.y
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: airbrushHandler
|
||||
acceptedDevices: PointerDevice.Airbrush
|
||||
acceptedPointerTypes: PointerDevice.Pen
|
||||
target: Image {
|
||||
parent: rect
|
||||
source: "resources/cursor-airbrush.png"
|
||||
visible: airbrushHandler.hovered
|
||||
x: airbrushHandler.point.position.x
|
||||
y: airbrushHandler.point.position.y
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: eraserHandler
|
||||
acceptedPointerTypes: PointerDevice.Eraser
|
||||
target: Image {
|
||||
parent: rect
|
||||
source: "resources/cursor-eraser.png"
|
||||
visible: eraserHandler.hovered
|
||||
x: eraserHandler.point.position.x
|
||||
y: eraserHandler.point.position.y - 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue