Skip to content

Commit

Permalink
Implement tap and hold gesture to color pick
Browse files Browse the repository at this point in the history
Can be configured in the preferences. Currently only one-finger tap and
hold is implemented and only to summon the color picker with it, until
someone needs something else.
  • Loading branch information
askmeaboutlo0m committed Sep 28, 2024
1 parent d4c8e68 commit a93fe55
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 17 deletions.
2 changes: 1 addition & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Unreleased Version 2.2.2-pre
* Fix: Clamp palette swatch sizes to more reasonable bounds. Thanks MachKerman for reporting.
* Server Feature: Allow checking session, operator and server account passwords via the API. This can be used to password-protect recordings or similar. Thanks Meru for contributing.
* Fix: Correct some UI scaling problems with brush outlines, canvas centering, dock toggling, pixel grid and transform handles. Thanks annoy for reporting.
* Feature: Extended touch tap gestures, among them two-finger tap to undo and three-finger tap to redo. Can be configured in the preferences under the Touch tab. Thanks InconsolableCellist and many others for suggesting.
* Feature: Extended touch tap gestures, among them two-finger tap to undo, three-finger tap to redo and tap-and-hold to summon the color picker. Can be configured in the preferences under the Touch tab. Thanks InconsolableCellist and many others for suggesting.
* Feature: Snap canvas rotation around 0° by default. If you don't want this, you can set the canvas shortcut or touch rotation to "free rotate canvas" instead.
* Feature: Show a color preview when picking a color from the canvas.

Expand Down
22 changes: 22 additions & 0 deletions src/desktop/dialogs/settingsdialog/touch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ void Touch::setUp(desktop::settings::Settings &settings, QVBoxLayout *layout)
initTouchActions(settings, utils::addFormSection(layout));
utils::addFormSeparator(layout);
initTapActions(settings, utils::addFormSection(layout));
utils::addFormSeparator(layout);
initTapAndHoldActions(settings, utils::addFormSection(layout));
}

void Touch::initMode(desktop::settings::Settings &settings, QFormLayout *form)
Expand Down Expand Up @@ -82,6 +84,26 @@ void Touch::initTapActions(
form->addRow(tr("Four-finger tap:"), fourFingerTap);
}

void Touch::initTapAndHoldActions(
desktop::settings::Settings &settings, QFormLayout *form)
{
QComboBox *oneFingerTapAndHold = new QComboBox;
for(QComboBox *tapAndHold : {oneFingerTapAndHold}) {
tapAndHold->addItem(
tr("No action"),
int(desktop::settings::TouchTapAndHoldAction::Nothing));
tapAndHold->addItem(
tr("Pick color"),
int(desktop::settings::TouchTapAndHoldAction::ColorPickMode));
}

settings.bindOneFingerTapAndHold(oneFingerTapAndHold, Qt::UserRole);

settings.bindTouchGestures(oneFingerTapAndHold, &QComboBox::setDisabled);

form->addRow(tr("One-finger tap and hold:"), oneFingerTapAndHold);
}

void Touch::initTouchActions(
desktop::settings::Settings &settings, QFormLayout *form)
{
Expand Down
3 changes: 3 additions & 0 deletions src/desktop/dialogs/settingsdialog/touch.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class Touch final : public Page {
void
initTapActions(desktop::settings::Settings &settings, QFormLayout *form);

void initTapAndHoldActions(
desktop::settings::Settings &settings, QFormLayout *form);

void
initTouchActions(desktop::settings::Settings &settings, QFormLayout *form);
};
Expand Down
16 changes: 16 additions & 0 deletions src/desktop/scene/canvasview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ CanvasView::CanvasView(QWidget *parent)
connect(
m_touch, &TouchHandler::touchTapActionActivated, this,
&CanvasView::touchTapActionActivated, Qt::DirectConnection);
connect(
m_touch, &TouchHandler::touchColorPicked, this,
&CanvasView::touchColorPick, Qt::DirectConnection);
connect(
m_touch, &TouchHandler::touchColorPickFinished, this,
&CanvasView::finishTouchColorPick, Qt::DirectConnection);
}


Expand Down Expand Up @@ -2075,6 +2081,16 @@ void CanvasView::pickColor(const QPointF &point, const QPointF &posf)
m_pickingColor = m_scene->setColorPick(mapToScene(posf.toPoint()), color);
}

void CanvasView::touchColorPick(const QPointF &posf)
{
pickColor(mapToCanvas(posf), posf);
}

void CanvasView::finishTouchColorPick()
{
m_scene->setColorPick(QPointF(), QColor());
}

void CanvasView::updateCanvasTransform(const std::function<void()> &block)
{
QPointF outlinePoint = fromCanvasTransform().map(m_prevoutlinepoint);
Expand Down
2 changes: 2 additions & 0 deletions src/desktop/scene/canvasview.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ private slots:
//! Drag the view
void moveDrag(const QPoint &point);
void pickColor(const QPointF &point, const QPointF &posf);
void touchColorPick(const QPointF &posf);
void finishTouchColorPick();

QTransform fromCanvasTransform() const;
QTransform toCanvasTransform() const;
Expand Down
6 changes: 6 additions & 0 deletions src/desktop/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ enum class TouchTapAction : int {
};
Q_ENUM_NS(TouchTapAction)

enum class TouchTapAndHoldAction : int {
Nothing,
ColorPickMode,
};
Q_ENUM_NS(TouchTapAndHoldAction)

enum class ThemePalette : int {
System,
Light,
Expand Down
1 change: 1 addition & 0 deletions src/desktop/settings_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ SETTING(oneFingerTap , OneFingerTap , "settings/input/
SETTING(twoFingerTap , TwoFingerTap , "settings/input/twofingertap" , int(TouchTapAction::Undo))
SETTING(threeFingerTap , ThreeFingerTap , "settings/input/threefingertap" , int(TouchTapAction::Redo))
SETTING(fourFingerTap , FourFingerTap , "settings/input/fourfingertap" , int(TouchTapAction::HideDocks))
SETTING(oneFingerTapAndHold , OneFingerTapAndHold , "settings/input/onefingertapandhold" , int(TouchTapAndHoldAction::ColorPickMode))
SETTING(tabletPressTimerDelay , TabletPressTimerDelay , "settings/input/tabletpresstimerdelay" , 500)
SETTING(touchGestures , TouchGestures , "settings/input/touchgestures" , false)
SETTING(onionSkinsFrameCount , OnionSkinsFrameCount , "onionskins/framecount" , 8)
Expand Down
97 changes: 81 additions & 16 deletions src/desktop/utils/touchhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C" {
#include "libshared/util/qtcompat.h"
#include <QDateTime>
#include <QGestureEvent>
#include <QTimer>

TouchHandler::TouchHandler(QObject *parent)
: QObject(parent)
Expand All @@ -19,8 +20,18 @@ TouchHandler::TouchHandler(QObject *parent)
, m_twoFingerTapAction(int(desktop::settings::TouchTapAction::Undo))
, m_threeFingerTapAction(int(desktop::settings::TouchTapAction::Redo))
, m_fourFingerTapAction(int(desktop::settings::TouchTapAction::HideDocks))
, m_oneFingerTapAndHoldAction(
int(desktop::settings::TouchTapAndHoldAction::ColorPickMode))
, m_tapTimer(Qt::CoarseTimer)
, m_tapAndHoldTimer(new QTimer(this))
{
m_tapAndHoldTimer->setTimerType(Qt::CoarseTimer);
m_tapAndHoldTimer->setSingleShot(true);
m_tapAndHoldTimer->setInterval(TAP_AND_HOLD_DELAY_MS);
connect(
m_tapAndHoldTimer, &QTimer::timeout, this,
&TouchHandler::triggerTapAndHold);

desktop::settings::Settings &settings = dpApp().settings();
settings.bindOneFingerTouch(this, &TouchHandler::setOneFingerTouchAction);
settings.bindTwoFingerPinch(this, &TouchHandler::setTwoFingerPinchAction);
Expand All @@ -29,6 +40,8 @@ TouchHandler::TouchHandler(QObject *parent)
settings.bindTwoFingerTap(this, &TouchHandler::setTwoFingerTapAction);
settings.bindThreeFingerTap(this, &TouchHandler::setThreeFingerTapAction);
settings.bindFourFingerTap(this, &TouchHandler::setFourFingerTapAction);
settings.bindOneFingerTapAndHold(
this, &TouchHandler::setOneFingerTapAndHoldAction);
}

bool TouchHandler::isTouchDrawEnabled() const
Expand Down Expand Up @@ -93,6 +106,7 @@ void TouchHandler::handleTouchBegin(QTouchEvent *event)
m_touchDrawBuffer.clear();
m_touchDragging = false;
m_touchRotating = false;
m_touchHeld = false;
m_maxTouchPoints = pointsCount;
m_tapTimer.setRemainingTime(TAP_MAX_DELAY_MS);
if(isTouchDrawEnabled() && pointsCount == 1 && !compat::isTouchPad(event)) {
Expand All @@ -105,7 +119,9 @@ void TouchHandler::handleTouchBegin(QTouchEvent *event)
qulonglong(event->timestamp()));
if(isTouchPanEnabled() || isTouchPinchOrTwistEnabled() ||
m_oneFingerTapAction !=
int(desktop::settings::TouchTapAction::Nothing)) {
int(desktop::settings::TouchTapAction::Nothing) ||
m_oneFingerTapAndHoldAction !=
int(desktop::settings::TouchTapAndHoldAction::Nothing)) {
// Buffer the touch first, since it might end up being the
// beginning of an action that involves multiple fingers.
m_touchDrawBuffer.append(
Expand All @@ -127,6 +143,14 @@ void TouchHandler::handleTouchBegin(QTouchEvent *event)
qulonglong(event->timestamp()));
m_touchMode = TouchMode::Moving;
}

if(pointsCount == 1 && m_touchMode != TouchMode::Drawing &&
m_oneFingerTapAndHoldAction !=
int(desktop::settings::TouchTapAndHoldAction::Nothing)) {
m_tapAndHoldTimer->start();
} else {
m_tapAndHoldTimer->stop();
}
}

void TouchHandler::handleTouchUpdate(
Expand All @@ -138,10 +162,23 @@ void TouchHandler::handleTouchUpdate(
m_maxTouchPoints = pointsCount;
}

if(isTouchDrawEnabled() &&
((pointsCount == 1 && m_touchMode == TouchMode::Unknown) ||
m_touchMode == TouchMode::Drawing) &&
!compat::isTouchPad(event)) {
if(pointsCount != 1) {
m_tapAndHoldTimer->stop();
}

m_touchPos = QPointF(0.0, 0.0);
for(const compat::TouchPoint &tp : compat::touchPoints(*event)) {
m_touchPos += compat::touchPos(tp);
}
m_touchPos /= pointsCount;

if(m_touchHeld) {
emit touchColorPicked(m_touchPos);
} else if(
isTouchDrawEnabled() &&
((pointsCount == 1 && m_touchMode == TouchMode::Unknown) ||
m_touchMode == TouchMode::Drawing) &&
!compat::isTouchPad(event)) {
QPointF posf = compat::touchPos(compat::touchPoints(*event).first());
DP_EVENT_LOG(
"touch_draw_update x=%f y=%f touching=%d type=%d device=%s "
Expand All @@ -155,6 +192,7 @@ void TouchHandler::handleTouchUpdate(
if(m_touchMode == TouchMode::Drawing) {
emit touchMoved(QDateTime::currentMSecsSinceEpoch(), posf);
} else { // Shouldn't happen, but we'll deal with it anyway.
m_tapAndHoldTimer->stop();
m_touchMode = TouchMode::Drawing;
emit touchPressed(
event, QDateTime::currentMSecsSinceEpoch(), posf);
Expand All @@ -172,6 +210,7 @@ void TouchHandler::handleTouchUpdate(
m_touchDrawBuffer.append(
{QDateTime::currentMSecsSinceEpoch(), posf});
} else {
m_tapAndHoldTimer->stop();
m_touchMode = TouchMode::Drawing;
flushTouchDrawBuffer();
emit touchMoved(QDateTime::currentMSecsSinceEpoch(), posf);
Expand All @@ -180,17 +219,15 @@ void TouchHandler::handleTouchUpdate(
} else {
m_touchMode = TouchMode::Moving;

QPointF startCenter, lastCenter, center;
QPointF startCenter, lastCenter;
for(const compat::TouchPoint &tp : compat::touchPoints(*event)) {
QPointF startPos = compat::touchStartPos(tp);
startCenter += startPos;
lastCenter += compat::touchLastPos(tp);
QPointF pos = compat::touchPos(tp);
center += pos;
// This might be a tap gesture. Don't start a drag until there's
// been sufficient movement on any of the fingers.
if(!m_touchDragging &&
squareDist(startPos - pos) > TAP_SLOP_SQUARED) {
squareDist(startPos - compat::touchPos(tp)) > TAP_SLOP_SQUARED) {
m_touchDragging = true;
}
}
Expand All @@ -206,9 +243,10 @@ void TouchHandler::handleTouchUpdate(
}
}

m_tapAndHoldTimer->stop();
startCenter /= pointsCount;
lastCenter /= pointsCount;
center /= pointsCount;
QPointF center = m_touchPos;

DP_EVENT_LOG(
"touch_update x=%f y=%f touching=%d type=%d device=%s "
Expand Down Expand Up @@ -294,12 +332,16 @@ void TouchHandler::handleTouchEnd(QTouchEvent *event, bool cancel)
{
event->accept();
const QList<compat::TouchPoint> &points = compat::touchPoints(*event);
if(isTouchDrawEnabled() &&
((m_touchMode == TouchMode::Unknown && !m_touchDrawBuffer.isEmpty() &&
(m_touchDragging ||
m_oneFingerTapAction ==
int(desktop::settings::TouchTapAction::Nothing))) ||
m_touchMode == TouchMode::Drawing)) {
m_tapAndHoldTimer->stop();
if(m_touchHeld) {
emit touchColorPickFinished();
} else if(
isTouchDrawEnabled() &&
((m_touchMode == TouchMode::Unknown && !m_touchDrawBuffer.isEmpty() &&
(m_touchDragging ||
m_oneFingerTapAction ==
int(desktop::settings::TouchTapAction::Nothing))) ||
m_touchMode == TouchMode::Drawing)) {
DP_EVENT_LOG(
"touch_draw_%s touching=%d type=%d device=%s points=%s "
"timestamp=%llu",
Expand Down Expand Up @@ -474,6 +516,29 @@ void TouchHandler::setFourFingerTapAction(int fourFingerTapAction)
m_fourFingerTapAction = fourFingerTapAction;
}

void TouchHandler::setOneFingerTapAndHoldAction(int oneFingerTapAndHoldAction)
{
m_oneFingerTapAndHoldAction = oneFingerTapAndHoldAction;
}

void TouchHandler::triggerTapAndHold()
{
m_tapAndHoldTimer->stop();
if(m_maxTouchPoints == 1 && m_touchMode != TouchMode::Drawing) {
switch(m_oneFingerTapAndHoldAction) {
case int(desktop::settings::TouchTapAndHoldAction::ColorPickMode):
m_touchHeld = true;
emit touchColorPicked(m_touchPos);
break;
default:
qWarning(
"Unknown one finger tap and hold action %d",
m_oneFingerTapAndHoldAction);
break;
}
}
}

qreal TouchHandler::adjustTwistRotation(qreal degrees) const
{
if(m_twoFingerTwistAction ==
Expand Down
11 changes: 11 additions & 0 deletions src/desktop/utils/touchhandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QPointF>

class QGestureEvent;
class QTimer;
class QTouchEvent;

class TouchHandler : public QObject {
Expand Down Expand Up @@ -38,10 +39,13 @@ class TouchHandler : public QObject {
void touchScrolledBy(qreal dx, qreal dy);
void touchZoomedRotated(qreal zoom, qreal rotation);
void touchTapActionActivated(int action);
void touchColorPicked(const QPointF &posf);
void touchColorPickFinished();

private:
static constexpr qreal TAP_SLOP_SQUARED = 16.0 * 16.0;
static constexpr int TAP_MAX_DELAY_MS = 1000;
static constexpr int TAP_AND_HOLD_DELAY_MS = 400;
static constexpr int DRAW_BUFFER_COUNT = 20;

enum class TouchMode { Unknown, Drawing, Moving };
Expand All @@ -58,6 +62,9 @@ class TouchHandler : public QObject {
void setTwoFingerTapAction(int twoFingerTapAction);
void setThreeFingerTapAction(int threeFingerTapAction);
void setFourFingerTapAction(int fourFingerTapAction);
void setOneFingerTapAndHoldAction(int oneFingerTapAndHoldAction);

void triggerTapAndHold();

qreal adjustTwistRotation(qreal degrees) const;
void flushTouchDrawBuffer();
Expand All @@ -66,6 +73,7 @@ class TouchHandler : public QObject {
bool m_touching = false;
bool m_touchDragging = false;
bool m_touchRotating = false;
bool m_touchHeld = false;
bool m_anyTabletEventsReceived = false;
int m_oneFingerTouchAction;
int m_twoFingerPinchAction;
Expand All @@ -74,16 +82,19 @@ class TouchHandler : public QObject {
int m_twoFingerTapAction;
int m_threeFingerTapAction;
int m_fourFingerTapAction;
int m_oneFingerTapAndHoldAction;
int m_maxTouchPoints = 0;
TouchMode m_touchMode = TouchMode::Unknown;
QVector<QPair<long long, QPointF>> m_touchDrawBuffer;
QPointF m_touchStartPos;
qreal m_touchStartZoom = 0.0;
qreal m_touchStartRotate = 0.0;
QPointF m_touchPos;
QPointF m_gestureStartPos;
qreal m_gestureStartZoom = 0.0;
qreal m_gestureStartRotation = 0.0;
QDeadlineTimer m_tapTimer;
QTimer *m_tapAndHoldTimer;
};

#endif
Loading

0 comments on commit a93fe55

Please sign in to comment.