feat: 光标移动到叠加层时释放捕获

This commit is contained in:
Xu 2024-01-02 22:36:16 +08:00
commit 8dadcfe2d2
7 changed files with 160 additions and 97 deletions

View file

@ -74,6 +74,10 @@ static POINT ScalingToSrc(POINT pt) noexcept {
}
CursorManager::~CursorManager() noexcept {
if (_isCapturedOnOverlay) {
ReleaseCapture();
}
if (_curClips != RECT{}) {
ClipCursor(nullptr);
}
@ -105,9 +109,7 @@ void CursorManager::Update() noexcept {
_hCursor = NULL;
_cursorPos = { std::numeric_limits<LONG>::max(),std::numeric_limits<LONG>::max() };
const ScalingOptions& options = ScalingWindow::Get().Options();
if (!options.IsDrawCursor()) {
if (!ScalingWindow::Get().Options().IsDrawCursor()) {
return;
}
@ -118,31 +120,45 @@ void CursorManager::Update() noexcept {
}
}
CURSORINFO ci{ sizeof(CURSORINFO) };
CURSORINFO ci{ .cbSize = sizeof(CURSORINFO) };
if (!GetCursorInfo(&ci)) {
Logger::Get().Win32Error("GetCursorPos 失败");
return;
}
if (ci.hCursor && ci.flags != CURSOR_SHOWING) {
if (!ci.hCursor || ci.flags != CURSOR_SHOWING) {
return;
}
// 不处于捕获状态置为 NULL
_hCursor = _isUnderCapture ? ci.hCursor : NULL;
_hCursor = ci.hCursor;
// 不处于捕获状态则位于叠加层上
_cursorPos = _isUnderCapture ? SrcToScaling(ci.ptScreenPos) : ci.ptScreenPos;
const RECT& scalingRect = ScalingWindow::Get().WndRect();
_cursorPos.x -= scalingRect.left;
_cursorPos.y -= scalingRect.top;
}
void CursorManager::OnCursorHoverOverlay() noexcept {
_isOnOverlay = true;
void CursorManager::IsCursorOnOverlay(bool value) noexcept {
if (_isOnOverlay == value) {
return;
}
_isOnOverlay = value;
_UpdateCursorClip();
}
void CursorManager::OnCursorLeaveOverlay() noexcept {
_isOnOverlay = false;
void CursorManager::IsCursorCapturedOnOverlay(bool value) noexcept {
if (_isCapturedOnOverlay == value) {
return;
}
_isCapturedOnOverlay = value;
if (value) {
SetCapture(ScalingWindow::Get().Handle());
} else {
ReleaseCapture();
}
_UpdateCursorClip();
}
@ -313,6 +329,13 @@ void CursorManager::_UpdateCursorClip() noexcept {
return;
}
if (_isCapturedOnOverlay) {
// 光标被叠加层捕获时将光标限制在输出区域内
_curClips = destRect;
ClipCursor(&destRect);
return;
}
const HWND hwndScaling = ScalingWindow::Get().Handle();
const RECT scalingRect = ScalingWindow::Get().WndRect();
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
@ -350,10 +373,16 @@ void CursorManager::_UpdateCursorClip() noexcept {
_StopCapture(cursorPos);
} else {
// 判断源窗口是否被遮挡
hwndCur = WindowFromPoint(hwndScaling, scalingRect, cursorPos, true);
// 主窗口未被遮挡
bool stopCapture = _isOnOverlay;
if (hwndCur != hwndSrc && (!IsChild(hwndSrc, hwndCur) || !((GetWindowStyle(hwndCur) & WS_CHILD)))) {
if (!stopCapture) {
// 判断源窗口是否被遮挡
hwndCur = WindowFromPoint(hwndScaling, scalingRect, cursorPos, true);
stopCapture = hwndCur != hwndSrc && (!IsChild(hwndSrc, hwndCur) || !((GetWindowStyle(hwndCur) & WS_CHILD)));
}
if (stopCapture) {
if (style | WS_EX_TRANSPARENT) {
SetWindowLongPtr(hwndScaling, GWL_EXSTYLE, style & ~WS_EX_TRANSPARENT);
}
@ -394,25 +423,52 @@ void CursorManager::_UpdateCursorClip() noexcept {
if (!PtInRect(&srcRect, newCursorPos)) {
// 跳过黑边
POINT clampedPos = {
std::clamp(cursorPos.x, destRect.left, destRect.right - 1),
std::clamp(cursorPos.y, destRect.top, destRect.bottom - 1)
};
if (WindowFromPoint(hwndScaling, scalingRect, clampedPos, false) == hwndScaling) {
if (!(style & WS_EX_TRANSPARENT)) {
SetWindowLongPtr(hwndScaling, GWL_EXSTYLE, style | WS_EX_TRANSPARENT);
if (_isOnOverlay) {
// 从内部移到外部
// 此时有 UI 贴边
if (newCursorPos.x >= srcRect.right) {
cursorPos.x += scalingRect.right - destRect.right;
} else if (newCursorPos.x < srcRect.left) {
cursorPos.x -= destRect.left - scalingRect.left;
}
_StartCapture(cursorPos);
if (newCursorPos.y >= srcRect.bottom) {
cursorPos.y += scalingRect.bottom - destRect.bottom;
} else if (newCursorPos.y < srcRect.top) {
cursorPos.y -= destRect.top - scalingRect.top;
}
if (MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONULL)) {
SetCursorPos(cursorPos.x, cursorPos.y);
} else {
// 目标位置不存在屏幕,则将光标限制在输出区域内
SetCursorPos(
std::clamp(cursorPos.x, destRect.left, destRect.right - 1),
std::clamp(cursorPos.y, destRect.top, destRect.bottom - 1)
);
}
} else {
// 要跳跃的位置被遮挡
if (style | WS_EX_TRANSPARENT) {
SetWindowLongPtr(hwndScaling, GWL_EXSTYLE, style & ~WS_EX_TRANSPARENT);
// 从外部移到内部
const POINT clampedPos {
std::clamp(cursorPos.x, destRect.left, destRect.right - 1),
std::clamp(cursorPos.y, destRect.top, destRect.bottom - 1)
};
if (WindowFromPoint(hwndScaling, scalingRect, clampedPos, false) == hwndScaling) {
if (!(style & WS_EX_TRANSPARENT)) {
SetWindowLongPtr(hwndScaling, GWL_EXSTYLE, style | WS_EX_TRANSPARENT);
}
_StartCapture(cursorPos);
} else {
// 要跳跃的位置被遮挡
if (style | WS_EX_TRANSPARENT) {
SetWindowLongPtr(hwndScaling, GWL_EXSTYLE, style & ~WS_EX_TRANSPARENT);
}
}
}
} else {
bool startCapture = true;
bool startCapture = !_isOnOverlay;
if (startCapture) {
// 判断源窗口是否被遮挡
@ -439,7 +495,7 @@ void CursorManager::_UpdateCursorClip() noexcept {
return;
}
if (!false && !_isUnderCapture) {
if (!_isUnderCapture && !_isOnOverlay) {
return;
}
@ -447,32 +503,32 @@ void CursorManager::_UpdateCursorClip() noexcept {
// 处理屏幕之间存在间隙的情况。解决办法是 _StopCapture 只在目标位置存在屏幕时才取消捕获,
// 当光标试图移动到间隙中时将被挡住。如果光标的速度足以跨越间隙,则它依然可以在屏幕间移动。
::GetCursorPos(&cursorPos);
POINT hostPos = false ? cursorPos : SrcToScaling(cursorPos);
POINT hostPos = _isOnOverlay ? cursorPos : SrcToScaling(cursorPos);
RECT clips{ LONG_MIN, LONG_MIN, LONG_MAX, LONG_MAX };
// left
RECT rect{ LONG_MIN, hostPos.y, scalingRect.left, hostPos.y + 1 };
if (!MonitorFromRect(&rect, MONITOR_DEFAULTTONULL)) {
clips.left = false ? destRect.left : srcRect.left;
clips.left = _isOnOverlay ? destRect.left : srcRect.left;
}
// top
rect = { hostPos.x, LONG_MIN, hostPos.x + 1,scalingRect.top };
if (!MonitorFromRect(&rect, MONITOR_DEFAULTTONULL)) {
clips.top = false ? destRect.top : srcRect.top;
clips.top = _isOnOverlay ? destRect.top : srcRect.top;
}
// right
rect = { scalingRect.right, hostPos.y, LONG_MAX, hostPos.y + 1 };
if (!MonitorFromRect(&rect, MONITOR_DEFAULTTONULL)) {
clips.right = false ? destRect.right : srcRect.right;
clips.right = _isOnOverlay ? destRect.right : srcRect.right;
}
// bottom
rect = { hostPos.x, scalingRect.bottom, hostPos.x + 1, LONG_MAX };
if (!MonitorFromRect(&rect, MONITOR_DEFAULTTONULL)) {
clips.bottom = false ? destRect.bottom : srcRect.bottom;
clips.bottom = _isOnOverlay ? destRect.bottom : srcRect.bottom;
}
if (clips != _curClips) {
@ -486,6 +542,8 @@ void CursorManager::_StartCapture(POINT cursorPos) noexcept {
return;
}
OutputDebugString(L"start");
const Renderer& renderer = ScalingWindow::Get().Renderer();
const RECT& srcRect = renderer.SrcRect();
const RECT& destRect = renderer.DestRect();
@ -527,6 +585,8 @@ void CursorManager::_StopCapture(POINT cursorPos, bool onDestroy) noexcept {
return;
}
OutputDebugString(L"stop");
if (_curClips != RECT{}) {
_curClips = {};
ClipCursor(nullptr);

View file

@ -23,9 +23,15 @@ public:
return _cursorPos;
}
void OnCursorHoverOverlay() noexcept;
bool IsCursorOnOverlay() const noexcept {
return _isOnOverlay;
}
void IsCursorOnOverlay(bool value) noexcept;
void OnCursorLeaveOverlay() noexcept;
bool IsCursorCapturedOnOverlay() const noexcept {
return _isCapturedOnOverlay;
}
void IsCursorCapturedOnOverlay(bool value) noexcept;
private:
void _ShowSystemCursor(bool show);
@ -48,6 +54,7 @@ private:
bool _isOnScalingWindow = false;
bool _isOnOverlay = false;
bool _isCapturedOnOverlay = false;
};
}

View file

@ -130,8 +130,6 @@ void ImGuiImpl::NewFrame() noexcept {
io.AddKeyEvent(ImGuiKey_Enter, false);
}
const bool originWantCaptureMouse = io.WantCaptureMouse;
ImGui::NewFrame();
// 将所有 ImGUI 窗口限制在视口内
@ -157,17 +155,7 @@ void ImGuiImpl::NewFrame() noexcept {
ImGui::SetWindowPos(window, pos);
}
CursorManager& cm = ScalingWindow::Get().CursorManager();
if (io.WantCaptureMouse) {
if (!originWantCaptureMouse) {
cm.OnCursorHoverOverlay();
}
} else {
if (originWantCaptureMouse) {
cm.OnCursorLeaveOverlay();
}
}
ScalingWindow::Get().CursorManager().IsCursorOnOverlay(io.WantCaptureMouse);
}
void ImGuiImpl::Draw() noexcept {
@ -256,42 +244,35 @@ void ImGuiImpl::_UpdateMousePos() noexcept {
}
void ImGuiImpl::ClearStates() noexcept {
/*ImGuiIO& io = ImGui::GetIO();
ImGuiIO& io = ImGui::GetIO();
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
std::fill(std::begin(io.MouseDown), std::end(io.MouseDown), false);
auto& cm = ScalingWindow::Get().CursorManager();
if (cm.IsCursorCapturedOnOverlay()) {
if (GetCapture() == MagApp::Get().GetHwndHost()) {
ReleaseCapture();
}
cm.OnCursorReleasedOnOverlay();
}
if (cm.IsCursorOnOverlay()) {
cm.OnCursorLeaveOverlay();
}
CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
cursorManager.IsCursorCapturedOnOverlay(false);
cursorManager.IsCursorOnOverlay(false);
// 更新状态
ImGui::BeginFrame();
ImGui::NewFrame();
ImGui::EndFrame();
if (io.WantCaptureMouse) {
// 拖拽时隐藏 UI 需渲染两帧才能重置 WantCaptureMouse
ImGui::BeginFrame();
ImGui::NewFrame();
ImGui::EndFrame();
}*/
}
}
void ImGuiImpl::MessageHandler(UINT msg, WPARAM wParam, LPARAM /*lParam*/) noexcept {
ImGuiIO& io = ImGui::GetIO();
/*if (!io.WantCaptureMouse) {
if (msg == WM_LBUTTONDOWN && MagApp::Get().GetOptions().Is3DGameMode()) {
MagApp::Get().GetRenderer().SetUIVisibility(false);
if (!io.WantCaptureMouse) {
// 3D 游戏模式下显示叠加层会使缩放窗口不透明,这时点击非叠加层区域应关闭叠加层
if (msg == WM_LBUTTONDOWN && ScalingWindow::Get().Options().Is3DGameMode()) {
ScalingWindow::Get().Renderer().IsOverlayVisible(false);
}
return std::nullopt;
}*/
return;
}
// 缩放窗口不会收到双击消息
switch (msg) {
@ -299,10 +280,7 @@ void ImGuiImpl::MessageHandler(UINT msg, WPARAM wParam, LPARAM /*lParam*/) noexc
case WM_RBUTTONDOWN:
{
if (!ImGui::IsAnyMouseDown()) {
if (!GetCapture()) {
SetCapture(ScalingWindow::Get().Handle());
}
//MagApp::Get().GetCursorManager().OnCursorCapturedOnOverlay();
ScalingWindow::Get().CursorManager().IsCursorCapturedOnOverlay(true);
}
io.MouseDown[msg == WM_LBUTTONDOWN ? 0 : 1] = true;
@ -314,10 +292,7 @@ void ImGuiImpl::MessageHandler(UINT msg, WPARAM wParam, LPARAM /*lParam*/) noexc
io.MouseDown[msg == WM_LBUTTONUP ? 0 : 1] = false;
if (!ImGui::IsAnyMouseDown()) {
if (GetCapture() == ScalingWindow::Get().Handle()) {
ReleaseCapture();
}
//MagApp::Get().GetCursorManager().OnCursorReleasedOnOverlay();
ScalingWindow::Get().CursorManager().IsCursorCapturedOnOverlay(false);
}
break;

View file

@ -88,11 +88,21 @@ void OverlayDrawer::SetUIVisibility(bool value) noexcept {
}
_isUIVisiable = value;
if (value) {
Logger::Get().Info("已开启叠加层");
} else {
if (!ScalingWindow::Get().Options().IsShowFPS()) {
_imguiImpl.ClearStates();
}
Logger::Get().Info("已关闭叠加层");
}
}
void OverlayDrawer::MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept {
_imguiImpl.MessageHandler(msg, wParam, lParam);
if (_isUIVisiable || ScalingWindow::Get().Options().IsShowFPS()) {
_imguiImpl.MessageHandler(msg, wParam, lParam);
}
}
static const std::wstring& GetAppLanguage() noexcept {

View file

@ -293,20 +293,22 @@ void Renderer::Render() noexcept {
_FrontendRender();
}
void Renderer::ToggleOverlay() noexcept {
if (!_overlayDrawer) {
_overlayDrawer = std::make_unique<OverlayDrawer>();
if (!_overlayDrawer->Initialize(&_frontendResources)) {
_overlayDrawer.reset();
Logger::Get().Error("初始化 OverlayDrawer 失败");
return;
void Renderer::IsOverlayVisible(bool value) noexcept {
if (value) {
if (!_overlayDrawer) {
_overlayDrawer = std::make_unique<OverlayDrawer>();
if (!_overlayDrawer->Initialize(&_frontendResources)) {
_overlayDrawer.reset();
Logger::Get().Error("初始化 OverlayDrawer 失败");
return;
}
}
}
if (_overlayDrawer->IsUIVisiable()) {
_overlayDrawer->SetUIVisibility(false);
} else {
_overlayDrawer->SetUIVisibility(true);
} else {
if (_overlayDrawer) {
_overlayDrawer->SetUIVisibility(false);
}
}
// 立即渲染一帧

View file

@ -22,8 +22,7 @@ public:
void Render() noexcept;
void ToggleOverlay() noexcept;
void IsOverlayVisible(bool value) noexcept;
bool IsOverlayVisible() noexcept;
const RECT& SrcRect() const noexcept {

View file

@ -204,7 +204,7 @@ void ScalingWindow::Render() noexcept {
}
void ScalingWindow::ToggleOverlay() noexcept {
_renderer->ToggleOverlay();
_renderer->IsOverlayVisible(!_renderer->IsOverlayVisible());
}
LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept {
@ -219,6 +219,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 在以下情况下会收到光标消息:
// 1、未捕获光标且缩放后的位置未被遮挡而缩放前的位置被遮挡
// 2、光标位于叠加层上
// 这时鼠标点击将激活源窗口
const HWND hwndForground = GetForegroundWindow();
if (hwndForground != _hwndSrc) {
if (!Win32Utils::SetForegroundWindow(_hwndSrc)) {
@ -233,12 +234,21 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
prevTimePoint = now;
// 模拟按键关闭开始菜单
INPUT inputs[4]{};
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_LWIN;
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = VK_LWIN;
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
INPUT inputs[]{
INPUT{
.type = INPUT_KEYBOARD,
.ki = KEYBDINPUT{
.wVk = VK_LWIN
}
},
INPUT{
.type = INPUT_KEYBOARD,
.ki = KEYBDINPUT{
.wVk = VK_LWIN,
.dwFlags = KEYEVENTF_KEYUP
}
}
};
SendInput((UINT)std::size(inputs), inputs, sizeof(INPUT));
// 等待系统处理