来自AI助手的总结
通过修改代码和使用Windows API,实现了在Live2D透明窗口中鼠标事件的穿透功能。
引入:
在Live2D官方的Demo中,我们将窗口设置为透明后,即使窗口透明,我们鼠标的事件仍然会被透明窗口处理,如何才能让鼠标的事件穿透到下层窗口呢?利用WindowsAPI,我们可以实现鼠标事件的穿透。
测试环境:VS2022-C++17-Debug-x64(需在VS2022中安装Qt扩展)
测试系统:Windows11
初期参考视频:見崎音羽(看完前4集即可)
参考资料:log159
代码:
修改:
在GLCore.h中,在GLCore类中,添加成员:
QPoint m_lastMousePos; // 添加鼠标位置缓存
在GLCore类中,添加成员函数:
void checkTransparencyMousePos_V4(const QPoint& screenPos);
bool underMouse_WinAPI();
两个函数的实现如下:
checkTransparencyMousePos_V4函数:
void GLCore::checkTransparencyMousePos_V4(const QPoint& screenPos)
{
// 将屏幕坐标转换为窗口客户区坐标
QPoint windowPos = this->mapFromGlobal(screenPos);
// 检查坐标是否在窗口内
if (!this->rect().contains(windowPos))
{
return;
}
// 考虑设备像素比并转换为OpenGL物理坐标
makeCurrent(); // 确保激活了OpenGL上下文
qreal dpr = this->devicePixelRatioF();
int physX = windowPos.x() * dpr;
int physY = windowPos.y() * dpr;
// 转换为OpenGL坐标系(Y轴翻转)
int physHeight = this->height() * dpr;
int glY = physHeight - physY - 1;
// 读取鼠标位置像素的RGBA值
unsigned char rgba[4];
glReadPixels(physX, glY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
HWND hwnd = (HWND)winId();
LONG ex = GetWindowLong(hwnd, GWL_EXSTYLE);
// Alpha值大于0 不透明
if (rgba[3] > 0)
{
//qDebug() << "不透明区域 - Alpha:" << rgba[3];
ex &= ~WS_EX_TRANSPARENT; // 禁用穿透
}
else
{
//qDebug() << "透明区域";
ex |= WS_EX_TRANSPARENT; // 启用穿透
}
SetWindowLong(hwnd, GWL_EXSTYLE, ex);
doneCurrent();
}
bool underMouse_WinAPI函数:
bool GLCore::underMouse_WinAPI()
{
// 窗口不可见或最小化
if (!isVisible() || window()->isMinimized())
{
return false;
}
HWND hwnd = reinterpret_cast<HWND>(winId());
RECT winRect;
if (!GetWindowRect(hwnd, &winRect))
{
return false;
}
POINT cursorPos;
if (!GetCursorPos(&cursorPos))
{
return false;
}
// 鼠标是否在窗口矩形内
const bool inWindow = (cursorPos.x >= winRect.left &&
cursorPos.x < winRect.right &&
cursorPos.y >= winRect.top &&
cursorPos.y < winRect.bottom);
// 不在窗口矩形内
if (!inWindow)
{
return false;
}
// 获取窗口样式
LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
bool isTransparent = (exStyle & WS_EX_TRANSPARENT);
// 透明穿透窗口
if (isTransparent)
{
return true;
}
// 非穿透窗口,确保窗口未被其他窗口遮挡
HWND hUnderMouse = WindowFromPoint(cursorPos);
return (hUnderMouse == hwnd) || IsChild(hwnd, hUnderMouse);
}
在控制模型刷新率的连接到的槽函数中,如:
connect(this->timer_flush_window, &QTimer::timeout, this, &GLCore::updateWindow);
在槽函数中,做出以下修改(完整的槽函数代码):
void GLCore::updateWindow()
{
this->update();
this->b_IsUnderMouse = this->underMouse_WinAPI();
if (b_IsUnderMouse)
{
}
else
{
qDebug() << "NoUnderMouse";
}
// 替换为安全的位置检测
if (b_IsUnderMouse)
{
QPoint currentPos = QCursor::pos();
qDebug() << "Pos : " << currentPos;
// 仅当鼠标位置变化时检测
if (currentPos != m_lastMousePos)
{
checkTransparencyMousePos_V4(currentPos);
m_lastMousePos = currentPos;
}
}
}
修改好后运行即可。
原理:
1. 函数GLCore::underMouse_WinAPI
分析:
该函数用于检查鼠标指针当前是否位于自身的窗口范围内,并动态根据窗口的可见性、透明性和层叠关系判断。
函数功能
- 判断窗口可见性: 判断窗口是否可见或处于最小化状态;
- 获取窗口的矩形边界: 确定窗口在屏幕上的位置和大小;
- 获取当前鼠标坐标: 确定鼠标指针当前的屏幕坐标;
- 检查鼠标是否在窗口内: 通过比较鼠标坐标和窗口边界确定鼠标是否位于窗口内;
- 处理透明性: 如果窗口是透明的,则返回
true
,表示鼠标在窗口内; - 检查遮挡关系: 通过
WindowFromPoint
来判断鼠标指针下方的窗口。如果指针位于该窗口上,则返回true
;如果该窗口是自身的子窗口,也返回true
。
2. 函数 GLCore::checkTransparencyMousePos_V4
分析
该函数用于确定鼠标指针的位置,并在需要时根据当前位置的 Alpha 值调整窗口的透明效果。这一过程包括读取当前鼠标的位置,进行 OpenGL 调用以获取像素的 RGBA 值,并根据 Alpha 值判断该像素是透明还是不透明,从而设置窗口的透明样式。
函数功能
- 坐标转换: 将给定的屏幕坐标转换为窗口客户区坐标。
- 窗口内位置检查: 确定鼠标位置是否位于窗口内部。
- 激活 OpenGL 上下文: 进行 OpenGL 的状态设置,以确保可以处理 OpenGL 函数调用。
- 转换为 OpenGL 坐标系: 将窗口坐标转换为物理坐标并进行 Y 轴翻转,以适应 OpenGL 的坐标系统。
- 读取 RGBA 值: 调用 glReadPixels 读取当前位置像素的 RGBA 值。
- 设置窗口样式: 根据 Alpha 值判断是否禁用或启用鼠标穿透。
代码分析与详细说明
void GLCore::checkTransparencyMousePos_V4(const QPoint& screenPos)
{
// 将屏幕坐标转换为窗口客户区坐标
QPoint windowPos = this->mapFromGlobal(screenPos);
mapFromGlobal(screenPos)
将全局屏幕坐标screenPos
转换为当前窗口的客户区坐标,以便后续计算。
// 检查坐标是否在窗口内
if (!this->rect().contains(windowPos))
{
return;
}
- 使用
this->rect().contains(windowPos)
检查转换后的坐标windowPos
是否在窗口的客户区域内。 - 如果不在,函数提前返回,不进行后续操作。
// 考虑设备像素比并转换为OpenGL物理坐标
makeCurrent(); // 确保激活了OpenGL上下文
qreal dpr = this->devicePixelRatioF();
int physX = windowPos.x() * dpr;
int physY = windowPos.y() * dpr;
makeCurrent()
确保 OpenGL 上下文是可用的,以使后续 OpenGL 函数可以正常工作。devicePixelRatioF()
获取设备的像素比(DPI),并根据此比率将窗口坐标转换为物理像素坐标physX
和physY
。
// 转换为OpenGL坐标系(Y轴翻转)
int physHeight = this->height() * dpr;
int glY = physHeight - physY - 1;
- 计算 OpenGL 使用的 Y 坐标。OpenGL 的坐标系统与普通 Windows 窗口坐标系统相反,Y 轴在屏幕上向下方向延伸。
// 读取鼠标位置像素的RGBA值
unsigned char rgba[4];
glReadPixels(physX, glY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
- 使用
glReadPixels
从当前 OpenGL 上下文读取指定位置的像素的 RGBA 值,存储在rgba
数组中。
HWND hwnd = (HWND)winId();
LONG ex = GetWindowLong(hwnd, GWL_EXSTYLE);
- 获取窗口句柄
hwnd
并读取当前窗口的扩展样式设置。
// Alpha值大于0 不透明
if (rgba[3] > 0)
{
ex &= ~WS_EX_TRANSPARENT; // 禁用穿透
}
else
{
ex |= WS_EX_TRANSPARENT; // 启用穿透
}
- 检查 Alpha 值(
rgba[3]
):- 如果 Alpha 值大于零,表示该区域不透明,禁用穿透效果(清除
WS_EX_TRANSPARENT
标志)。 - 如果 Alpha 值等于零,表示该区域透明,启用穿透效果(添加
WS_EX_TRANSPARENT
标志)。
- 如果 Alpha 值大于零,表示该区域不透明,禁用穿透效果(清除
SetWindowLong(hwnd, GWL_EXSTYLE, ex);
doneCurrent();
}
- 将更新后的窗口样式写回去,应用所做的更改。
- 最后调用
doneCurrent()
,确保 OpenGL 上下文的状态被清理或恢复。
参考资料:
- SetWindowLongA 函数 (winuser.h) – Win32 apps | Microsoft Learn
- GetWindowLongA 函数 (winuser.h) – Win32 apps | Microsoft Learn
- 扩展窗口样式 (Winuser.h) – Win32 apps | Microsoft Learn
- GetWindowRect 函数 (winuser.h) – Win32 apps | Microsoft Learn
运行结果:
![]() |
视频展示: |
© 版权声明
THE END
暂无评论内容