• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30182373935

26 Jul 2026 01:08AM UTC coverage: 15.307%. First build
30182373935

Pull #2146

github

web-flow
Merge f73f8c4da into 13afd939a
Pull Request #2146: Per-platform relative mouse motion (pointer lock) instead of cursor warping

1 of 128 new or added lines in 6 files covered. (0.78%)

30390 of 198538 relevant lines covered (15.31%)

983.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/lib/score/graphics/InfiniteScroller.cpp
1
#include <score/graphics/InfiniteScroller.hpp>
2
#include <score/tools/Cursor.hpp>
3
#include <score/tools/PointerLock.hpp>
4

5
#include <QCoreApplication>
6
#include <QGraphicsItem>
7
#include <QGraphicsScene>
8
#include <QGraphicsSceneMouseEvent>
9
#include <QGraphicsView>
10
#include <QGuiApplication>
11
#include <QMouseEvent>
12
#include <QPointer>
13
#include <QScreen>
14
#include <QTimer>
15
#include <QWidget>
16
#include <QWindow>
17
#include <qpa/qwindowsysteminterface.h>
18

19
#include <cmath>
20

21
namespace score
22
{
23
namespace
24
{
25
bool relativeSession{};
26
QPointer<QWindow> relativeWindow{};
27
QPointF virtualPos{};
28

NEW
29
void endRelativeSession()
×
30
{
NEW
31
  PointerLock::endRelative();
×
NEW
32
  relativeSession = false;
×
NEW
33
  relativeWindow.clear();
×
NEW
34
}
×
35

NEW
36
QWindow* windowOf(QGraphicsItem& item) noexcept
×
37
{
NEW
38
  auto* scene = item.scene();
×
NEW
39
  if(!scene)
×
NEW
40
    return nullptr;
×
41

NEW
42
  for(auto* view : scene->views())
×
NEW
43
    if(auto* w = view->window()->windowHandle())
×
NEW
44
      return w;
×
NEW
45
  return nullptr;
×
NEW
46
}
×
47

48
// QGuiApplication drops mouse moves that do not change the cursor position,
49
// which a locked pointer never does.
NEW
50
void onRelativeMotion(QPointF delta)
×
51
{
NEW
52
  auto* win = relativeWindow.data();
×
NEW
53
  if(!win)
×
NEW
54
    return;
×
55

NEW
56
  const QRectF geom = win->geometry();
×
NEW
57
  virtualPos += delta;
×
NEW
58
  virtualPos.setX(qBound(geom.left(), virtualPos.x(), geom.right()));
×
NEW
59
  virtualPos.setY(qBound(geom.top(), virtualPos.y(), geom.bottom()));
×
60

NEW
61
  const QPointF local = win->mapFromGlobal(virtualPos);
×
NEW
62
  QMouseEvent ev{
×
63
      QEvent::MouseMove,
64
      local,
65
      local,
66
      virtualPos,
67
      Qt::NoButton,
NEW
68
      qGuiApp->mouseButtons(),
×
NEW
69
      qGuiApp->keyboardModifiers()};
×
NEW
70
  QCoreApplication::sendEvent(win, &ev);
×
NEW
71
}
×
72

73
// Last resort for a backend that ends the drag without the widget ever seeing
74
// a release: an item left grabbed is as bad as a pointer left locked. Deferred,
75
// so that a release the widget does get keeps going through the usual path.
NEW
76
void onRelativeRelease()
×
77
{
NEW
78
  QTimer::singleShot(0, qGuiApp, [] {
×
NEW
79
    if(!relativeSession)
×
NEW
80
      return;
×
81

NEW
82
    auto* win = relativeWindow.data();
×
NEW
83
    endRelativeSession();
×
84

NEW
85
    if(win)
×
NEW
86
      QWindowSystemInterface::handleMouseEvent(
×
NEW
87
          win, win->mapFromGlobal(virtualPos), virtualPos, Qt::NoButton, Qt::LeftButton,
×
NEW
88
          QEvent::MouseButtonRelease, qGuiApp->keyboardModifiers());
×
NEW
89
  });
×
NEW
90
}
×
91
}
92

93
QRectF InfiniteScroller::currentGeometry{};
94
double InfiniteScroller::origValue{};
95
double InfiniteScroller::currentSpeed{};
96
double InfiniteScroller::currentDelta{};
97

NEW
98
void InfiniteScroller::start(QGraphicsItem& self, double orig)
×
99
{
NEW
100
  currentDelta = 0.;
×
NEW
101
  origValue = orig;
×
102

103
#if !defined(__EMSCRIPTEN__)
NEW
104
  self.setCursor(QCursor(Qt::BlankCursor));
×
105
#endif
NEW
106
  auto* screen = qGuiApp->screenAt(QCursor::pos());
×
NEW
107
  if(!screen)
×
NEW
108
    screen = qGuiApp->primaryScreen();
×
NEW
109
  currentGeometry = screen->availableGeometry();
×
110

NEW
111
  auto* window = windowOf(self);
×
NEW
112
  relativeWindow = window;
×
NEW
113
  virtualPos = QCursor::pos();
×
114
  relativeSession
NEW
115
      = PointerLock::beginRelative(window, &onRelativeMotion, &onRelativeRelease);
×
NEW
116
}
×
117

NEW
118
void InfiniteScroller::move_free(QGraphicsSceneMouseEvent* event)
×
119
{
NEW
120
  const double ratio = qGuiApp->keyboardModifiers() & Qt::CTRL ? 0.2 : 1.;
×
121

122
  // Decided per move rather than latched at start: the lock is granted
123
  // asynchronously and can be refused or dropped mid-drag, and the pointer
124
  // positions below still work when it is not held.
NEW
125
  if(relativeSession && PointerLock::active())
×
126
  {
NEW
127
    const double delta = ratio * PointerLock::takeDelta().y();
×
NEW
128
    currentSpeed = delta;
×
NEW
129
    currentDelta += delta;
×
NEW
130
    return;
×
131
  }
132

NEW
133
  const double delta = (event->screenPos().y() - event->lastScreenPos().y());
×
NEW
134
  if(std::abs(delta) < 500)
×
135
  {
NEW
136
    currentSpeed = ratio * delta;
×
NEW
137
    currentDelta += ratio * delta;
×
NEW
138
  }
×
139

NEW
140
  const double top = currentGeometry.top();
×
NEW
141
  const double bottom = currentGeometry.bottom();
×
NEW
142
  if(event->screenPos().y() <= top + 100)
×
143
  {
NEW
144
    score::setCursorPos(QPointF(event->screenPos().x(), bottom - 101));
×
NEW
145
  }
×
NEW
146
  else if(event->screenPos().y() >= bottom - 100)
×
147
  {
NEW
148
    score::setCursorPos(QPointF(event->screenPos().x(), top + 101));
×
NEW
149
  }
×
NEW
150
}
×
151

NEW
152
double InfiniteScroller::move(QGraphicsSceneMouseEvent* event)
×
153
{
NEW
154
  move_free(event);
×
155

NEW
156
  const double max = currentGeometry.height();
×
NEW
157
  double v = origValue - currentDelta / max;
×
NEW
158
  if(v <= 0.)
×
159
  {
NEW
160
    currentDelta = origValue * max;
×
NEW
161
    v = 0.;
×
NEW
162
  }
×
NEW
163
  else if(v >= 1.)
×
164
  {
NEW
165
    currentDelta = ((origValue - 1.) * max);
×
NEW
166
    v = 1.;
×
NEW
167
  }
×
168

NEW
169
  return v;
×
170
}
171

NEW
172
void InfiniteScroller::stop(QGraphicsItem& self, QGraphicsSceneMouseEvent* event)
×
173
{
NEW
174
  if(relativeSession)
×
NEW
175
    endRelativeSession();
×
176
  else
NEW
177
    score::setCursorPos(event->buttonDownScreenPos(Qt::LeftButton));
×
178

NEW
179
  self.unsetCursor();
×
NEW
180
}
×
181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc