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:
Shawn Rutledge 2019-12-14 16:02:21 +01:00
parent c48b0902b5
commit 8e822e981d
15 changed files with 582 additions and 20 deletions

View File

@ -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;

View File

@ -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();
}
}

View File

@ -84,6 +84,7 @@ private:
private:
bool m_hovered = false;
bool m_hoveredTablet = false;
};
QT_END_NAMESPACE

View File

@ -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
{

View File

@ -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; }

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}
}
}