Skip to content

Commit 7fa5db5

Browse files
committed
Editor: Improve the behaviour of scrolling on macOS
It was a serious pain with the old scroll behaviour, and now it works better and more controllable, especially on macOS.
1 parent c9dba11 commit 7fa5db5

File tree

3 files changed

+177
-46
lines changed

3 files changed

+177
-46
lines changed

Editor/common_features/graphicsworkspace.cpp

Lines changed: 141 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@
1818

1919
#include "graphicsworkspace.h"
2020
#include "logger.h"
21+
#include <cmath>
2122
#include <QElapsedTimer>
2223
#include <QRubberBand>
24+
#include <QGesture>
25+
#include <QGestureEvent>
26+
#include <QPanGesture>
27+
#include <QPinchGesture>
28+
#ifdef __APPLE__
29+
#include <QNativeGestureEvent>
30+
#endif
2331

2432
#include <editing/_scenes/level/lvl_scene.h>
2533
#include <editing/_scenes/world/wld_scene.h>
@@ -88,6 +96,13 @@ GraphicsWorkspace::GraphicsWorkspace(QWidget *parent) :
8896
rubberBandSelectionMode = Qt::IntersectsItemShape;
8997
this->setMouseTracking(true);
9098
this->setRubberBandSelectionMode(Qt::IntersectsItemShape);
99+
#ifdef __APPLE__
100+
this->setAttribute(Qt::WA_AcceptTouchEvents, true);
101+
#endif
102+
this->grabGesture(Qt::PanGesture);
103+
#ifndef __APPLE__
104+
this->grabGesture(Qt::PinchGesture);
105+
#endif
91106

92107
movement = MOVE_IDLE;
93108

@@ -102,11 +117,17 @@ GraphicsWorkspace::GraphicsWorkspace(QWidget *parent) :
102117
scaleMin = 0.01;
103118
scaleMax = 20.0;
104119

120+
scaleGestureFactor = 1.0;
121+
105122
connect(&Mover, SIGNAL(timeout()), this, SLOT(doMove()));
106123
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(replayLastMouseEvent(int)));
107124
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(replayLastMouseEvent(int)));
108125

109126
setTransformationAnchor(QGraphicsView::AnchorViewCenter);
127+
128+
#ifdef __APPLE__
129+
installEventFilter(this);
130+
#endif
110131
}
111132

112133
GraphicsWorkspace::~GraphicsWorkspace()
@@ -183,10 +204,13 @@ void GraphicsWorkspace::doMove()
183204
{
184205
if((movement & MOVE_LEFT) != 0)
185206
moveLeft();
207+
186208
if((movement & MOVE_RIGHT) != 0)
187209
moveRight();
210+
188211
if((movement & MOVE_UP) != 0)
189212
moveUp();
213+
190214
if((movement & MOVE_DOWN) != 0)
191215
moveDown();
192216
}
@@ -291,6 +315,7 @@ void GraphicsWorkspace::focusOutEvent(QFocusEvent *event)
291315
void GraphicsWorkspace::wheelEvent(QWheelEvent *event)
292316
{
293317
int modS = 128;
318+
float modX, modY;
294319
// int modS_h = modS;
295320
// bool rtl = (qApp->layoutDirection() == Qt::RightToLeft);
296321

@@ -307,54 +332,34 @@ void GraphicsWorkspace::wheelEvent(QWheelEvent *event)
307332
int deltaX = delta.x();
308333
int deltaY = delta.y();
309334

335+
modX = std::abs(modS * ((deltaX / 8.0f) / 15.0f));
336+
modY = std::abs(modS * ((deltaY / 8.0f) / 15.0f));
337+
310338
if((event->modifiers() & Qt::ControlModifier) != 0)
311339
{
312-
// Scale the view / do the zoom
313-
if(deltaY > 0)
314-
{
315-
if(zoomValue * scaleFactor >= scaleMax) return;
316-
// Zoom in
317-
zoomValue *= scaleFactor;
318-
scale(scaleFactor, scaleFactor);
319-
emit zoomValueChanged(qRound(zoomValue * 100));
320-
emit zoomValueChanged(QString::number(qRound(zoomValue * 100)));
321-
}
322-
else if(deltaY < 0)
323-
{
324-
if(zoomValue * scaleFactor <= scaleMin) return;
325-
// Zooming out
326-
zoomValue *= 1.0 / scaleFactor;
327-
scale(1.0 / scaleFactor, 1.0 / scaleFactor);
328-
emit zoomValueChanged(qRound(zoomValue * 100));
329-
emit zoomValueChanged(QString::number(qRound(zoomValue * 100)));
330-
}
340+
modY = 1.0 + std::abs(0.15 * ((deltaY / 8.0f) / 15.0f));
341+
// qDebug() << "Zoom factor: " << modY << scaleFactor << deltaY;
342+
eventZoom(deltaY, modY);
331343

332344
replayLastMouseEvent();
333345
return;
334346
}
335347

336-
auto *hBar = horizontalScrollBar();
337-
auto *vBar = verticalScrollBar();
338-
339348
if((event->modifiers() & Qt::AltModifier) != 0)
340349
{
341350
// Ensure values aren't already swapped
342351
if(delta.x() == 0 && delta.y() != 0)
343352
{
344353
deltaY = delta.x();
345354
deltaX = delta.y();
355+
modY = std::abs(modS * ((deltaX / 8.0f) / 15.0f));
356+
modX = std::abs(modS * ((deltaY / 8.0f) / 15.0f));
346357
}
347358
}
348359

349-
if(deltaX > 0)
350-
hBar->setValue(hBar->value() - modS);
351-
else if(deltaX < 0)
352-
hBar->setValue(hBar->value() + modS);
360+
// qDebug() << "Wheel Delta: " << deltaX << deltaY;
353361

354-
if(deltaY > 0)
355-
vBar->setValue(vBar->value() - modS);
356-
else if(deltaY < 0)
357-
vBar->setValue(vBar->value() + modS);
362+
eventScroll(deltaX, modX, deltaY, modY);
358363

359364
//event->accept();
360365
replayLastMouseEvent();
@@ -363,6 +368,112 @@ void GraphicsWorkspace::wheelEvent(QWheelEvent *event)
363368
scene()->update();
364369
}
365370

371+
bool GraphicsWorkspace::event(QEvent *event)
372+
{
373+
if(event->type() == QEvent::Gesture)
374+
return gestureEvent(static_cast<QGestureEvent*>(event));
375+
376+
return QGraphicsView::event(event);
377+
}
378+
379+
#ifdef __APPLE__
380+
bool GraphicsWorkspace::eventFilter(QObject *obj, QEvent *event)
381+
{
382+
if(event->type() == QEvent::NativeGesture)
383+
{
384+
// Handle double-tap on Magic Mouse on macOS when "smart zoom" option is activated
385+
QNativeGestureEvent *gesture = static_cast<QNativeGestureEvent*>(event);
386+
Q_ASSERT(gesture);
387+
388+
if(gesture->gestureType() == Qt::NativeGestureType::SmartZoomNativeGesture)
389+
{
390+
if(qRound(zoomValue * 100) == 100)
391+
setZoom(2.5);
392+
else
393+
setZoom(1.0);
394+
return true;
395+
}
396+
}
397+
398+
return QGraphicsView::eventFilter(obj, event);
399+
}
400+
#endif
401+
402+
bool GraphicsWorkspace::gestureEvent(QGestureEvent *event)
403+
{
404+
if(QGesture *pan = event->gesture(Qt::PanGesture))
405+
gesturePan(static_cast<QPanGesture *>(pan));
406+
else if(QGesture *pinch = event->gesture(Qt::PinchGesture))
407+
gesturePinch(static_cast<QPinchGesture *>(pinch));
408+
409+
return true;
410+
}
411+
412+
void GraphicsWorkspace::gesturePan(QPanGesture *gesture)
413+
{
414+
QPointF delta = gesture->delta();
415+
eventScroll(delta.x(), std::abs(delta.x()), delta.y(), std::abs(delta.y()));
416+
}
417+
418+
void GraphicsWorkspace::gesturePinch(QPinchGesture *gesture)
419+
{
420+
QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
421+
422+
if(changeFlags & QPinchGesture::ScaleFactorChanged)
423+
{
424+
scaleGestureFactor = gesture->totalScaleFactor();
425+
qDebug() << "gesturePinch(): zoom by" << gesture->scaleFactor() << "->" << scaleGestureFactor;
426+
}
427+
428+
if(gesture->state() == Qt::GestureFinished)
429+
{
430+
zoomValue *= gesture->scaleFactor();
431+
scale(scaleGestureFactor, scaleGestureFactor);
432+
emit zoomValueChanged(qRound(zoomValue * 100));
433+
emit zoomValueChanged(QString::number(qRound(zoomValue * 100)));
434+
scaleGestureFactor = 1.0;
435+
}
436+
}
437+
438+
void GraphicsWorkspace::eventScroll(int dirx, int x, int diry, int y)
439+
{
440+
auto *hBar = horizontalScrollBar();
441+
auto *vBar = verticalScrollBar();
442+
443+
if(dirx > 0)
444+
hBar->setValue(hBar->value() - x);
445+
else if(dirx < 0)
446+
hBar->setValue(hBar->value() + x);
447+
448+
if(diry > 0)
449+
vBar->setValue(vBar->value() - y);
450+
else if(diry < 0)
451+
vBar->setValue(vBar->value() + y);
452+
}
453+
454+
void GraphicsWorkspace::eventZoom(int dir, float delta)
455+
{
456+
// Scale the view / do the zoom
457+
if(dir > 0)
458+
{
459+
if(zoomValue * delta >= scaleMax) return;
460+
// Zoom in
461+
zoomValue *= delta;
462+
scale(delta, delta);
463+
emit zoomValueChanged(qRound(zoomValue * 100));
464+
emit zoomValueChanged(QString::number(qRound(zoomValue * 100)));
465+
}
466+
else if(dir < 0)
467+
{
468+
if(zoomValue * delta <= scaleMin) return;
469+
// Zooming out
470+
zoomValue *= 1.0 / delta;
471+
scale(1.0 / delta, 1.0 / delta);
472+
emit zoomValueChanged(qRound(zoomValue * 100));
473+
emit zoomValueChanged(QString::number(qRound(zoomValue * 100)));
474+
}
475+
}
476+
366477

367478

368479
void GraphicsWorkspace::mousePressEvent(QMouseEvent *event)

Editor/common_features/graphicsworkspace.h

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
#include <QStyleHintReturnMask>
2121
#include <QStyleOptionRubberBand>
2222

23+
class QGestureEvent;
24+
class QPanGesture;
25+
class QPinchGesture;
26+
2327

2428
// Internal replacement for QMouseEvent which is impossible to copy at Qt6
2529
class PGEMouseEvent
@@ -104,12 +108,13 @@ private slots:
104108

105109
private:
106110
QTimer Mover;
107-
enum Movements{
108-
MOVE_IDLE=0,
109-
MOVE_LEFT=1,
110-
MOVE_UP=2,
111-
MOVE_DOWN=4,
112-
MOVE_RIGHT=8
111+
enum Movements
112+
{
113+
MOVE_IDLE = 0,
114+
MOVE_LEFT = 1,
115+
MOVE_UP = 2,
116+
MOVE_DOWN = 4,
117+
MOVE_RIGHT = 8
113118
};
114119
int movement;
115120
int step;
@@ -120,21 +125,36 @@ private slots:
120125
double scaleMin;
121126
double scaleMax;
122127

128+
qreal scaleGestureFactor;
129+
123130
protected:
124-
virtual void focusOutEvent ( QFocusEvent * event );
131+
virtual void focusOutEvent(QFocusEvent *event) override;
132+
133+
virtual void keyPressEvent(QKeyEvent *event) override;
134+
virtual void keyReleaseEvent(QKeyEvent *event) override;
135+
virtual void wheelEvent(QWheelEvent *event) override;
136+
137+
virtual bool event(QEvent *event) override;
138+
#ifdef __APPLE__
139+
virtual bool eventFilter(QObject *obj, QEvent *event) override;
140+
#endif
141+
private:
142+
bool gestureEvent(QGestureEvent *event);
125143

126-
virtual void keyPressEvent ( QKeyEvent * event );
127-
virtual void keyReleaseEvent ( QKeyEvent * event );
128-
virtual void wheelEvent ( QWheelEvent * event );
144+
void gesturePan(QPanGesture *gesture);
145+
void gesturePinch(QPinchGesture *gesture);
129146

147+
void eventScroll(int dirx, int x, int diry, int y);
148+
void eventZoom(int dir, float delta);
130149

150+
protected:
131151
///
132152
/// My Crazy "Bycicle" :P
133153
///
134-
virtual void mousePressEvent( QMouseEvent *event );
135-
virtual void mouseMoveEvent( QMouseEvent * event);
136-
virtual void mouseReleaseEvent( QMouseEvent * event);
137-
virtual void mouseDoubleClickEvent(QMouseEvent *event);
154+
virtual void mousePressEvent(QMouseEvent *event) override;
155+
virtual void mouseMoveEvent(QMouseEvent *event) override;
156+
virtual void mouseReleaseEvent(QMouseEvent *event) override;
157+
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
138158

139159
void mouseMoveEventHandler(PGEMouseEvent &event);
140160
void storeMouseEvent(const PGEMouseEvent &event);
@@ -176,8 +196,7 @@ private slots:
176196
QRubberBand *rubberBandX;
177197

178198
protected slots:
179-
void replayLastMouseEvent(int x=0);
180-
199+
void replayLastMouseEvent(int x = 0);
181200
};
182201

183202
#endif // GRAPHICSWORKSPACE_H

changelog.editor.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ beta2
5858
- Added "Health" and "Reserved item" per-player test settings
5959
- Fixed an inability to select a music file that contains multiple repeating spaces
6060
- Added the custom music file validation with notices showing for not existing files, legacy, or heavy formats
61+
- Improved the behaviour of the wheel/touchpad scrolling events handling, especially on macOS with the Magic Mouse's touchpad
6162

6263
Editor 0.3.2
6364
- Added support for an element (Block, BGO, NPC), section, and level-wide Extra Settings

0 commit comments

Comments
 (0)