Qt源码解析: QCursor::pos()

本文最后更新于:2024年3月14日 凌晨

前情提要

QCursor::pos()看起来简单,返回鼠标坐标

其实在高DPI缩放下,返回的确是逻辑坐标,我们可以通过这个函数,管中窥豹

理解Qt是如何在逻辑坐标系与物理坐标系之间转换的

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
QPoint QCursor::pos()
{
return QCursor::pos(QGuiApplication::primaryScreen());
}

QPoint QCursor::pos(const QScreen *screen)
{
if (screen) {
if (const QPlatformCursor *cursor = screen->handle()->cursor()) {
const QPlatformScreen *ps = screen->handle();
QPoint nativePos = cursor->pos(); //原生物理坐标
ps = ps->screenForPosition(nativePos); //寻找nativePos所在Screen
return QHighDpi::fromNativePixels(nativePos, ps->screen()); //
}
}
return QGuiApplicationPrivate::lastCursorPosition.toPoint();
}

我们先来看一下QCursor::pos()的逻辑啊

有两个重载,无参版本默认把主显示器传给了有参版本

这里,我们可能会疑惑,诶,为什么需要QScreen参数呢,光标坐标和屏幕有关吗

别急,接着往下看

首先,获取了原生屏幕指针(QPlatformScreen),然后通过QPlatformCursor获取到了光标的原生物理坐标(nativePos

接着通过ps = ps->screenForPosition(nativePos)更新了屏幕指针,这是为什么呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*!
Find the sibling screen corresponding to \a globalPos.
Returns this screen if no suitable screen is found at the position.
*/
const QPlatformScreen *QPlatformScreen::screenForPosition(const QPoint &point) const
{
if (!geometry().contains(point)) {
const auto screens = virtualSiblings();
for (const QPlatformScreen *screen : screens) {
if (screen->geometry().contains(point))
return screen;
}
}
return this;
}

我们可以看到,注释和代码里都提到了一个词:sibling

来看一下Qt文档的解释

1
2
3
4
QList<QScreen *> QScreen::virtualSiblings() const
//Get the screen's virtual siblings.

//The virtual siblings are the screen instances sharing the same virtual desktop. They share a common coordinate system, and windows can //freely be moved or positioned across them without having to be re-created.

所以,sibling screens也就是一个虚拟桌面内的所有相邻显示器,一般情况下与qApp->screens()相等

因此,screenForPosition()会遍历屏幕,找到鼠标光标所在的那一块

所以我们不必纠结,为什么一开始传入的是primaryScreen(),会不会不准确

其实传入什么screen都可以,只要在同一个虚拟桌面内,会自行定位

这里传入一个指针,是为了定位虚拟桌面而已

more more more:QHighDpi::fromNativePixels

顾名思义,我们已经知道了他可以从物理坐标(原生像素)转化为逻辑坐标

那么稍微看一下源码吧

1
2
3
4
5
6
7
template <typename T, typename C>
T fromNativePixels(const T &value, const C *context)
{
QPoint nativePosition = position(value); //转换为QPoint
QHighDpiScaling::ScaleAndOrigin so = QHighDpiScaling::scaleAndOrigin(context, &nativePosition);
return scale(value, qreal(1) / so.factor, so.origin);
}

OK,很短,就三行,我们先来看看QHighDpiScaling::scaleAndOrigin

1
2
3
4
5
6
7
8
9
10
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)
{
if (!m_active)
return { qreal(1), QPoint() };
if (!platformScreen)
return { m_factor, QPoint() }; // the global factor
const QPlatformScreen *actualScreen = nativePosition ?
platformScreen->screenForPosition(*nativePosition) : platformScreen;
return { m_factor * screenSubfactor(actualScreen), actualScreen->geometry().topLeft() };
}

这里再次调用screenForPosition()定位了光标所在屏幕,非常严谨

返回值有俩:

  • 计算缩放比:m_factor * screenSubfactor(actualScreen),巴拉巴拉bomb,可能是内部缩放比 * 外部缩放比?不管了
  • 计算光标所在屏幕的左上角物理坐标:actualScreen->geometry().topLeft()

这第二个就是重点了,跟我们自行计算的方法有异曲同工之妙,不过他这里获取到的是真真正正的物理坐标

然后调用scale(value, qreal(1) / so.factor, so.origin)进行最后的计算

1
2
3
4
inline QPoint scale(const QPoint &pos, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
return (pos - origin) * scaleFactor + origin;
}

先减去左上角的坐标,归一化为(0,0),然后直接乘除缩放比,再把左上角加回来 hhhhhh

啊,哈,啊哈哈哈

Peace

Ref

qtbase/src/gui/kernel/qhighdpiscaling_p.h] - Codebrowser


Qt源码解析: QCursor::pos()
https://mrbeancpp.github.io/2024/03/13/Qt源码解析-QCursor-pos/
作者
MrBeanC
发布于
2024年3月13日
许可协议