Krita Source Code Documentation
Loading...
Searching...
No Matches
KisCanvasNavigationActionStrategyTouch.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2026 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QTouchEvent>
10
11#include <kis_algebra_2d.h>
12#include <kis_canvas2.h>
14
16
17namespace
18{
19
20#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
21 using TouchPoint = QTouchEvent::TouchPoint;
22#else
23 using TouchPoint = QEventPoint;
24#endif
25
26bool testIfPointPressed(const TouchPoint &point) {
27 auto state = static_cast<Qt::TouchPointState>(point.state());
28 return KisTouchShortcut::pressedOnlyTouchStates().testFlag(state);
29}
30
31QPointF calculateBasePointFromEvent(const QTouchEvent *tevent)
32{
33 QPointF result;
34
35#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
36 auto points = tevent->touchPoints();
37 auto pointFunctor = [] (const auto &point) {
38 return point.pos();
39 };
40#else
41 auto points = tevent->points();
42 auto pointFunctor = [] (const auto &point) {
43 return point.position();
44 };
45#endif
46
47 auto [sum, count] =
48 std::accumulate(points.begin(),
49 points.end(),
50 std::pair<QPointF, int>{},
51 [&](std::pair<QPointF, int> result, const TouchPoint &point) {
52 if (!testIfPointPressed(point)) {
53 return result;
54 }
55 result.first += pointFunctor(point);
56 result.second++;
57
58 return result;
59 });
60
61 if (count > 0) {
62 result = sum / qreal(count);
63 }
64
65 return result;
66}
67
68} // namespace
69
71 const QTouchEvent *startEvent,
72 KisCanvas2 *canvas)
74 , m_canvas(canvas)
75{
77 calculateBasePointFromEvent(startEvent));
78 m_nonRoundedZoom = canvas->viewConverter()->zoom();
79}
80
82{
83 return event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchUpdate
84 || event->type() == QEvent::TouchEnd;
85}
86
88{
89 if (event->type() != QEvent::TouchUpdate) return;
90
91 QTouchEvent *tevent = dynamic_cast<QTouchEvent *>(event);
92
93#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
94 auto points = tevent->touchPoints();
95#else
96 auto points = tevent->points();
97#endif
98
99 auto point0_it = std::find_if(points.begin(), points.end(), &testIfPointPressed);
100 if (point0_it == points.end())
101 return;
102
103 auto point1_it = std::find_if(std::next(point0_it), points.end(), &testIfPointPressed);
104 if (point1_it == points.end()) {
105 if (flags() & (ZoomEnabled | RotationEnabled)) {
106 return;
107 } else {
108 // we are doing pan-only, this action can be connected to
109 // a single-finger gesture
110 point1_it = point0_it;
111 }
112 }
113
114#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
115 const QPointF p0 = point0_it->pos();
116 const QPointF p1 = point1_it->pos();
117#else
118 const QPointF p0 = point0_it->position();
119 const QPointF p1 = point1_it->position();
120#endif
121
122 const QPointF slope = p1 - p0;
123
124 KisCanvasController *controller = static_cast<KisCanvasController *>(m_canvas->canvasController());
125
127
133 bool needsSeparatePanAction = true;
134
135 if (flags().testFlag(PanEnabled)) {
136 adjustedStillPoint.second = calculateBasePointFromEvent(tevent);
137 }
138
139 if (flags().testFlag(ZoomEnabled)) {
140 const qreal dist = KisAlgebra2D::norm(slope);
141
142 qreal scaleDelta = qFuzzyIsNull(m_lastDistance) ? 1.0 : dist / m_lastDistance;
143
144 // Make sure none of the TouchPoints are too close together, which
145 // throws off the zoom calculations. This also addresses a glitch
146 // where a newly pressed TouchPoint can incorrectly report another
147 // existing TouchPoint's coordinates instead of its own.
148
149 if (dist < 10) {
150 scaleDelta = 1.0;
151 }
152
153 // Workaround: only apply the zoom delta if it's not too
154 // outlandish. TouchPoint coordinates are not always 100% reliable.
155
156 if (qAbs(scaleDelta) < 0.8 || qAbs(scaleDelta) > 1.2) {
157 // just skip the current zoom step
158 scaleDelta = 1.0;
159 }
160
161 const qreal newNonRoundedZoom = m_nonRoundedZoom * scaleDelta;
162
163 if (flags().testFlag(ZoomDescrete)) {
165 if (scaleDelta > 1.0) {
166 const qreal nextExpectedZoom =
167 converter->findNextZoom(converter->zoom(), converter->standardZoomLevels());
168 if (newNonRoundedZoom >= nextExpectedZoom) {
169 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, nextExpectedZoom, adjustedStillPoint);
170 needsSeparatePanAction = false;
171 }
172 } else if (scaleDelta < 1.0) {
173 const qreal prevExpectedZoom =
174 converter->findPrevZoom(converter->zoom(), converter->standardZoomLevels());
175 if (newNonRoundedZoom <= prevExpectedZoom) {
176 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, prevExpectedZoom, adjustedStillPoint);
177 needsSeparatePanAction = false;
178 }
179 }
180
181 } else {
182 controller->setZoom(KoZoomMode::ZOOM_CONSTANT, newNonRoundedZoom, adjustedStillPoint);
183 needsSeparatePanAction = false;
184 }
185
186 m_lastDistance = dist;
187 m_nonRoundedZoom = newNonRoundedZoom;
188 }
189
190 if (flags().testFlag(RotationEnabled)) {
191 const qreal currentAngle = std::atan2(slope.y(), slope.x());
192 qreal rotationAngle = 0.0;
193
194 if (flags().testFlag(RotationDescrete)) {
195 rotationAngle = canvasRotationAngleDescrete(currentAngle, m_rotationData);
196 } else {
197 KisCanvasController *controller = static_cast<KisCanvasController *>(m_canvas->canvasController());
198 rotationAngle = canvasRotationAngleContinuous(currentAngle, controller->rotation(), m_rotationData);
199 }
200
201 if (!qFuzzyIsNull(rotationAngle)) {
202 controller->rotateCanvas(rotationAngle, adjustedStillPoint);
203 needsSeparatePanAction = false;
204 }
205 }
206
207 if (needsSeparatePanAction) {
209 const QPointF currentStillPointViewPosition = converter->documentToWidget(m_actionStillPoint.docPoint());
210 const QPoint canvasOffset((adjustedStillPoint.viewPoint() - currentStillPointViewPosition).toPoint());
211 controller->pan(-canvasOffset);
212 }
213}
QPointF p0
QPointF p1
KisCoordinatesConverter * coordinatesConverter
const KoViewConverter * viewConverter() const override
void rotateCanvas(qreal angle, const std::optional< KoViewTransformStillPoint > &stillPoint, bool isNativeGesture=false)
KisCanvasNavigationActionStrategyTouch(Flags flags, const QTouchEvent *startEvent, KisCanvas2 *canvas)
static qreal canvasRotationAngleContinuous(qreal currentRotationOffset, qreal currectCanvasRotation, SnappedRotationData &data)
static qreal canvasRotationAngleDescrete(qreal currentRotationOffset, SnappedRotationData &data)
static qreal findNextZoom(qreal currentZoom, const QVector< qreal > &zoomLevels)
_Private::Traits< T >::Result documentToWidget(const T &obj) const
QVector< qreal > standardZoomLevels() const
KoViewTransformStillPoint makeWidgetStillPoint(const QPointF &viewPoint) const override
Creates a still point that links the viewPoint of the widget to the corresponding point of the image.
static qreal findPrevZoom(qreal currentZoom, const QVector< qreal > &zoomLevels)
static Qt::TouchPointStates pressedOnlyTouchStates()
KoCanvasController * canvasController() const
void pan(const QPoint &distance) override
void setZoom(KoZoomMode::Mode mode, qreal zoom) override
virtual void zoom(qreal *zoomX, qreal *zoomY) const
void zoom(qreal *zoomX, qreal *zoomY) const override
@ ZOOM_CONSTANT
zoom x %
Definition KoZoomMode.h:24
static bool qFuzzyIsNull(half h)
qreal norm(const T &a)