Skip to content

Commit

Permalink
Extend touch gestures
Browse files Browse the repository at this point in the history
Allowing tap gestures to undo, redo and some other stuff, as well as
different rotation modes when twisting. Can be configured in the
preferences under the new Touch tab.

Implements #1174.
  • Loading branch information
askmeaboutlo0m committed Sep 27, 2024
1 parent 2b5c177 commit a9a838f
Show file tree
Hide file tree
Showing 21 changed files with 547 additions and 89 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +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.

2024-08-09 Version 2.2.2-beta.3
* Fix: Use more accurate timers for performance profiles if the platform supports it.
Expand Down
2 changes: 2 additions & 0 deletions src/desktop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ target_sources(drawpile PRIVATE
dialogs/settingsdialog/shortcuts.h
dialogs/settingsdialog/tools.cpp
dialogs/settingsdialog/tools.h
dialogs/settingsdialog/touch.cpp
dialogs/settingsdialog/touch.h
dialogs/startdialog.cpp
dialogs/startdialog.h
dialogs/startdialog/browse.cpp
Expand Down
3 changes: 3 additions & 0 deletions src/desktop/dialogs/settingsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "desktop/dialogs/settingsdialog/servers.h"
#include "desktop/dialogs/settingsdialog/shortcuts.h"
#include "desktop/dialogs/settingsdialog/tools.h"
#include "desktop/dialogs/settingsdialog/touch.h"
#include "desktop/dialogs/settingsdialog/userinterface.h"
#include "desktop/main.h"
#include "desktop/settings.h"
Expand Down Expand Up @@ -89,6 +90,8 @@ SettingsDialog::SettingsDialog(
new settingsdialog::UserInterface(m_settings, this), true},
{"dialog-input-devices", tr("Input"),
new settingsdialog::Input(m_settings, this), true},
{"hand", tr("Touch"), new settingsdialog::Touch(m_settings, this),
true},
{"tools", tr("Tools"), new settingsdialog::Tools(m_settings, this),
true},
{"network-modem", tr("Network"),
Expand Down
34 changes: 0 additions & 34 deletions src/desktop/dialogs/settingsdialog/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,40 +127,6 @@ void Input::initTablet(
form->addRow(tr("Driver:"), driver);
#endif

utils::addFormSpacer(form);

auto *touchMode = utils::addRadioGroup(
form, tr("Touch mode:"), true,
{
{tr("Touchscreen"), false},
{tr("Gestures"), true},
});
settings.bindTouchGestures(touchMode);

auto *oneTouch = utils::addRadioGroup(
form, tr("One-finger input:"), true,
{
{tr("None"), int(desktop::settings::OneFingerTouchAction::Nothing)},
{tr("Draw"), int(desktop::settings::OneFingerTouchAction::Draw)},
{tr("Pan"), int(desktop::settings::OneFingerTouchAction::Pan)},
{tr("Guess"), int(desktop::settings::OneFingerTouchAction::Guess)},
});
settings.bindOneFingerTouch(oneTouch);
settings.bindTouchGestures(
oneTouch->button(int(desktop::settings::OneFingerTouchAction::Draw)),
&QWidget::setDisabled);

auto *twoTouch = new utils::EncapsulatedLayout;
twoTouch->setContentsMargins(0, 0, 0, 0);
auto *zoom = new QCheckBox(tr("Pinch to zoom"));
settings.bindTwoFingerZoom(zoom);
twoTouch->addWidget(zoom);
auto *rotate = new QCheckBox(tr("Twist to rotate"));
settings.bindTwoFingerRotate(rotate);
twoTouch->addWidget(rotate);
twoTouch->addStretch();
form->addRow(tr("Touch gestures:"), twoTouch);

auto *testerLayout = new QVBoxLayout;
auto *testerLabel = new QLabel(tr("Test your tablet here:"));
testerLabel->setAlignment(Qt::AlignHCenter);
Expand Down
128 changes: 128 additions & 0 deletions src/desktop/dialogs/settingsdialog/touch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "desktop/dialogs/settingsdialog/touch.h"
#include "desktop/settings.h"
#include "desktop/utils/widgetutils.h"
#include <QButtonGroup>
#include <QComboBox>
#include <QFormLayout>
#include <QVBoxLayout>
#include <QWidget>

namespace dialogs {
namespace settingsdialog {

Touch::Touch(desktop::settings::Settings &settings, QWidget *parent)
: Page(parent)
{
init(settings);
}

void Touch::setUp(desktop::settings::Settings &settings, QVBoxLayout *layout)
{
initMode(settings, utils::addFormSection(layout));
utils::addFormSeparator(layout);
initTouchActions(settings, utils::addFormSection(layout));
utils::addFormSeparator(layout);
initTapActions(settings, utils::addFormSection(layout));
}

void Touch::initMode(desktop::settings::Settings &settings, QFormLayout *form)
{
QButtonGroup *touchMode = utils::addRadioGroup(
form, tr("Touch mode:"), true,
{
{tr("Touchscreen"), false},
{tr("Gestures"), true},
});
settings.bindTouchGestures(touchMode);
}

void Touch::initTapActions(
desktop::settings::Settings &settings, QFormLayout *form)
{
QComboBox *oneFingerTap = new QComboBox;
QComboBox *twoFingerTap = new QComboBox;
QComboBox *threeFingerTap = new QComboBox;
QComboBox *fourFingerTap = new QComboBox;
for(QComboBox *tap :
{oneFingerTap, twoFingerTap, threeFingerTap, fourFingerTap}) {
tap->addItem(
tr("No action"), int(desktop::settings::TouchTapAction::Nothing));
tap->addItem(tr("Undo"), int(desktop::settings::TouchTapAction::Undo));
tap->addItem(tr("Redo"), int(desktop::settings::TouchTapAction::Redo));
tap->addItem(
tr("Hide docks"),
int(desktop::settings::TouchTapAction::HideDocks));
tap->addItem(
tr("Toggle color picker"),
int(desktop::settings::TouchTapAction::ColorPicker));
tap->addItem(
tr("Toggle eraser"),
int(desktop::settings::TouchTapAction::Eraser));
tap->addItem(
tr("Toggle erase mode"),
int(desktop::settings::TouchTapAction::EraseMode));
tap->addItem(
tr("Toggle recolor mode"),
int(desktop::settings::TouchTapAction::RecolorMode));
}

settings.bindOneFingerTap(oneFingerTap, Qt::UserRole);
settings.bindTwoFingerTap(twoFingerTap, Qt::UserRole);
settings.bindThreeFingerTap(threeFingerTap, Qt::UserRole);
settings.bindFourFingerTap(fourFingerTap, Qt::UserRole);

settings.bindTouchGestures(twoFingerTap, &QComboBox::setDisabled);
settings.bindTouchGestures(threeFingerTap, &QComboBox::setDisabled);
settings.bindTouchGestures(fourFingerTap, &QComboBox::setDisabled);

form->addRow(tr("One-finger tap:"), oneFingerTap);
form->addRow(tr("Two-finger tap:"), twoFingerTap);
form->addRow(tr("Three-finger tap:"), threeFingerTap);
form->addRow(tr("Four-finger tap:"), fourFingerTap);
}

void Touch::initTouchActions(
desktop::settings::Settings &settings, QFormLayout *form)
{
QComboBox *oneFingerTouch = new QComboBox;
oneFingerTouch->setSizeAdjustPolicy(QComboBox::AdjustToContents);
oneFingerTouch->addItem(
tr("No action"), int(desktop::settings::OneFingerTouchAction::Nothing));
oneFingerTouch->addItem(
tr("Draw"), int(desktop::settings::OneFingerTouchAction::Draw));
oneFingerTouch->addItem(
tr("Pan canvas"), int(desktop::settings::OneFingerTouchAction::Pan));
oneFingerTouch->addItem(
tr("Guess"), int(desktop::settings::OneFingerTouchAction::Guess));
settings.bindOneFingerTouch(oneFingerTouch, Qt::UserRole);
form->addRow(tr("One-finger touch:"), oneFingerTouch);

QComboBox *twoFingerPinch = new QComboBox;
twoFingerPinch->setSizeAdjustPolicy(QComboBox::AdjustToContents);
twoFingerPinch->addItem(
tr("No action"), int(desktop::settings::TwoFingerPinchAction::Nothing));
twoFingerPinch->addItem(
tr("Zoom"), int(desktop::settings::TwoFingerPinchAction::Zoom));
settings.bindTwoFingerPinch(twoFingerPinch, Qt::UserRole);
form->addRow(tr("Two-finger pinch:"), twoFingerPinch);

QComboBox *twoFingerTwist = new QComboBox;
twoFingerTwist->setSizeAdjustPolicy(QComboBox::AdjustToContents);
twoFingerTwist->addItem(
tr("No action"), int(desktop::settings::TwoFingerPinchAction::Nothing));
twoFingerTwist->addItem(
tr("Rotate canvas"),
int(desktop::settings::TwoFingerTwistAction::Rotate));
twoFingerTwist->addItem(
tr("Free rotate canvas"),
int(desktop::settings::TwoFingerTwistAction::RotateNoSnap));
twoFingerTwist->addItem(
tr("Ratchet rotate canvas"),
int(desktop::settings::TwoFingerTwistAction::RotateDiscrete));
settings.bindTwoFingerTwist(twoFingerTwist, Qt::UserRole);
form->addRow(tr("Two-finger twist:"), twoFingerTwist);
}

} // namespace settingsdialog
} // namespace dialogs
39 changes: 39 additions & 0 deletions src/desktop/dialogs/settingsdialog/touch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DESKTOP_DIALOGS_SETTINGSDIALOG_TOUCH_H
#define DESKTOP_DIALOGS_SETTINGSDIALOG_TOUCH_H
#include "desktop/dialogs/settingsdialog/page.h"

class QFormLayout;

namespace desktop {
namespace settings {
class Settings;
}
}

namespace dialogs {
namespace settingsdialog {

class Touch final : public Page {
Q_OBJECT
public:
Touch(desktop::settings::Settings &settings, QWidget *parent = nullptr);

protected:
void
setUp(desktop::settings::Settings &settings, QVBoxLayout *layout) override;

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

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

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

}
}

#endif
24 changes: 20 additions & 4 deletions src/desktop/dialogs/systeminfodialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,26 @@ QString SystemInfoDialog::getSystemInfo() const
QStringLiteral("One-finger touch action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::OneFingerTouchAction>()
.valueToKey(settings.oneFingerTouch()));
info += QStringLiteral("Two-finger zoom: %1\n")
.arg(boolToYesNo(settings.twoFingerZoom()));
info += QStringLiteral("Two-finger rotate: %1\n")
.arg(boolToYesNo(settings.twoFingerRotate()));
info +=
QStringLiteral("Two-finger pinch action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TwoFingerPinchAction>()
.valueToKey(settings.twoFingerPinch()));
info +=
QStringLiteral("Two-finger twist action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TwoFingerTwistAction>()
.valueToKey(settings.twoFingerTwist()));
info += QStringLiteral("One-finger tap action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TouchTapAction>()
.valueToKey(settings.oneFingerTap()));
info += QStringLiteral("Two-finger tap action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TouchTapAction>()
.valueToKey(settings.twoFingerTap()));
info += QStringLiteral("Three-finger tap action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TouchTapAction>()
.valueToKey(settings.threeFingerTap()));
info += QStringLiteral("Four-finger tap action: %1\n")
.arg(QMetaEnum::fromType<desktop::settings::TouchTapAction>()
.valueToKey(settings.fourFingerTap()));
info += QStringLiteral("Touch gestures: %1\n")
.arg(boolToYesNo(settings.touchGestures()));
info += QStringLiteral("\n");
Expand Down
45 changes: 43 additions & 2 deletions src/desktop/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2257,8 +2257,12 @@ void MainWindow::toggleTabletEventLog()
DP_event_log_write_meta("Tablet enabled: %d", settings.tabletEvents());
DP_event_log_write_meta("Tablet eraser action: %d", settings.tabletEraserAction());
DP_event_log_write_meta("One-finger touch action: %d", settings.oneFingerTouch());
DP_event_log_write_meta("Two-finger rotate: %d", settings.twoFingerRotate());
DP_event_log_write_meta("Two-finger zoom: %d", settings.twoFingerZoom());
DP_event_log_write_meta("Two-finger pinch action: %d", settings.twoFingerPinch());
DP_event_log_write_meta("Two-finger twist action: %d", settings.twoFingerTwist());
DP_event_log_write_meta("One-finger tap action: %d", settings.oneFingerTap());
DP_event_log_write_meta("Two-finger tap action: %d", settings.twoFingerTap());
DP_event_log_write_meta("Three-finger tap action: %d", settings.threeFingerTap());
DP_event_log_write_meta("Four-finger tap action: %d", settings.fourFingerTap());
DP_event_log_write_meta("Gestures: %d", settings.touchGestures());
} else {
showErrorMessageWithDetails(tr("Error opening tablet event log."), DP_error());
Expand Down Expand Up @@ -3192,6 +3196,43 @@ void MainWindow::handleToggleAction(int action)
}

// clang-format on
void MainWindow::handleTouchTapAction(int action)
{
switch(action) {
case int(desktop::settings::TouchTapAction::Undo):
getAction("undo")->trigger();
break;
case int(desktop::settings::TouchTapAction::Redo):
getAction("redo")->trigger();
break;
case int(desktop::settings::TouchTapAction::HideDocks):
getAction("hidedocks")->trigger();
break;
case int(desktop::settings::TouchTapAction::ColorPicker):
if(m_dockToolSettings->currentTool() == tools::Tool::PICKER) {
m_dockToolSettings->setPreviousTool();
} else {
m_dockToolSettings->setTool(tools::Tool::PICKER);
}
break;
case int(desktop::settings::TouchTapAction::Eraser):
if(m_dockToolSettings->currentTool() == tools::Tool::ERASER) {
m_dockToolSettings->setPreviousTool();
} else {
m_dockToolSettings->setTool(tools::Tool::ERASER);
}
break;
case int(desktop::settings::TouchTapAction::EraseMode):
getAction("currenterasemode")->trigger();
break;
case int(desktop::settings::TouchTapAction::RecolorMode):
getAction("currentrecolormode")->trigger();
break;
default:
qWarning("Unknown tap action %d", action);
break;
}
}

void MainWindow::setNotificationsMuted(bool muted)
{
Expand Down
1 change: 1 addition & 0 deletions src/desktop/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public slots:
void dropImage(const QImage &image);
void dropUrl(const QUrl &url);
void handleToggleAction(int action);
void handleTouchTapAction(int action);

void savePreResetImageAs();
void discardPreResetImage();
Expand Down
5 changes: 5 additions & 0 deletions src/desktop/scene/canvasview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,23 @@ CanvasView::CanvasView(QWidget *parent)
connect(
m_touch, &TouchHandler::touchZoomedRotated, this,
&CanvasView::touchZoomRotate, Qt::DirectConnection);
connect(
m_touch, &TouchHandler::touchTapActionActivated, this,
&CanvasView::touchTapActionActivated, Qt::DirectConnection);
}


void CanvasView::setTouchUseGestureEvents(bool useGestureEvents)
{
if(useGestureEvents && !m_useGestureEvents) {
DP_EVENT_LOG("grab gesture events");
viewport()->grabGesture(Qt::TapGesture);
viewport()->grabGesture(Qt::PanGesture);
viewport()->grabGesture(Qt::PinchGesture);
m_useGestureEvents = true;
} else if(!useGestureEvents && m_useGestureEvents) {
DP_EVENT_LOG("ungrab gesture events");
viewport()->ungrabGesture(Qt::TapGesture);
viewport()->ungrabGesture(Qt::PanGesture);
viewport()->ungrabGesture(Qt::PinchGesture);
m_useGestureEvents = false;
Expand Down
1 change: 1 addition & 0 deletions src/desktop/scene/canvasview.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class CanvasView final : public QGraphicsView {
void savePreResetStateDismissed();

void toggleActionActivated(int action);
void touchTapActionActivated(int action);

public slots:
//! Set the size of the brush preview outline
Expand Down
3 changes: 3 additions & 0 deletions src/desktop/scene/scenewrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ void SceneWrapper::connectMainWindow(MainWindow *mainWindow)
connect(
m_view, &CanvasView::toggleActionActivated, mainWindow,
&MainWindow::handleToggleAction);
connect(
m_view, &CanvasView::touchTapActionActivated, mainWindow,
&MainWindow::handleTouchTapAction);
connect(
m_view, &CanvasView::reconnectRequested, mainWindow,
&MainWindow::reconnect);
Expand Down
Loading

0 comments on commit a9a838f

Please sign in to comment.