mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-07 03:36:44 +02:00
490 lines
16 KiB
C++
490 lines
16 KiB
C++
#include "pch.h"
|
|
#include "WindowKeyboardSnap.h"
|
|
|
|
#include <FancyZonesLib/Settings.h>
|
|
#include <FancyZonesLib/trace.h>
|
|
#include <FancyZonesLib/WindowUtils.h>
|
|
#include <FancyZonesLib/WorkArea.h>
|
|
#include <FancyZonesLib/util.h>
|
|
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/winapi_error.h>
|
|
|
|
bool WindowKeyboardSnap::Snap(HWND window, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<HMONITOR>& monitors)
|
|
{
|
|
return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && SnapHotkeyBasedOnZoneNumber(window, vkCode, monitor, activeWorkAreas, monitors);
|
|
}
|
|
|
|
bool WindowKeyboardSnap::Snap(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<std::pair<HMONITOR, RECT>>& monitors)
|
|
{
|
|
if (!activeWorkAreas.contains(monitor))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// clean previous extension data
|
|
m_extendData.Reset();
|
|
|
|
const auto& currentWorkArea = activeWorkAreas.at(monitor);
|
|
if (monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
|
|
{
|
|
// Multi monitor environment.
|
|
// First, try to stay on the same monitor
|
|
bool success = MoveByDirectionAndPosition(window, windowRect, vkCode, false, currentWorkArea.get());
|
|
if (success)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Try to snap on another monitor
|
|
success = SnapBasedOnPositionOnAnotherMonitor(window, windowRect, vkCode, monitor, activeWorkAreas, monitors);
|
|
if (success)
|
|
{
|
|
// Unsnap from previous work area
|
|
currentWorkArea->Unsnap(window);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
else
|
|
{
|
|
// Single monitor environment, or combined multi-monitor environment.
|
|
return MoveByDirectionAndPosition(window, windowRect, vkCode, true, currentWorkArea.get());
|
|
}
|
|
}
|
|
|
|
bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
|
|
{
|
|
if (!activeWorkAreas.contains(monitor))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// continue extension process
|
|
const auto& workArea = activeWorkAreas.at(monitor);
|
|
return Extend(window, windowRect, vkCode, workArea.get());
|
|
}
|
|
|
|
bool WindowKeyboardSnap::SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR current, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<HMONITOR>& monitors)
|
|
{
|
|
// clean previous extension data
|
|
m_extendData.Reset();
|
|
|
|
if (current && monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
|
|
{
|
|
// Multi monitor environment.
|
|
auto currMonitor = std::find(std::begin(monitors), std::end(monitors), current);
|
|
do
|
|
{
|
|
if (activeWorkAreas.contains(*currMonitor))
|
|
{
|
|
const auto& workArea = activeWorkAreas.at(*currMonitor);
|
|
|
|
if (MoveByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea.get()))
|
|
{
|
|
// unassign from previous work area
|
|
for (auto& [_, prevWorkArea] : activeWorkAreas)
|
|
{
|
|
if (prevWorkArea && workArea != prevWorkArea)
|
|
{
|
|
prevWorkArea->Unsnap(window);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
|
|
if (vkCode == VK_RIGHT)
|
|
{
|
|
currMonitor = std::next(currMonitor);
|
|
if (currMonitor == std::end(monitors))
|
|
{
|
|
currMonitor = std::begin(monitors);
|
|
}
|
|
}
|
|
else if (vkCode == VK_LEFT)
|
|
{
|
|
if (currMonitor == std::begin(monitors))
|
|
{
|
|
currMonitor = std::end(monitors);
|
|
}
|
|
currMonitor = std::prev(currMonitor);
|
|
}
|
|
}
|
|
} while (*currMonitor != current);
|
|
}
|
|
else
|
|
{
|
|
if (activeWorkAreas.contains(current))
|
|
{
|
|
const auto& workArea = activeWorkAreas.at(current);
|
|
bool moved = MoveByDirectionAndIndex(window, vkCode, FancyZonesSettings::settings().moveWindowAcrossMonitors /* cycle through zones */, workArea.get());
|
|
|
|
if (FancyZonesSettings::settings().restoreSize && !moved)
|
|
{
|
|
FancyZonesWindowUtils::RestoreWindowOrigin(window);
|
|
FancyZonesWindowUtils::RestoreWindowSize(window);
|
|
}
|
|
|
|
return moved;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WindowKeyboardSnap::SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR current, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas, const std::vector<std::pair<HMONITOR, RECT>>& monitors)
|
|
{
|
|
// Extract zones from all other monitors and target one of them
|
|
std::vector<RECT> zoneRects;
|
|
std::vector<std::pair<ZoneIndex, WorkArea*>> zoneRectsInfo;
|
|
RECT currentMonitorRect{ .top = 0, .bottom = -1 };
|
|
|
|
for (const auto& [monitor, monitorRect] : monitors)
|
|
{
|
|
if (monitor == current)
|
|
{
|
|
currentMonitorRect = monitorRect;
|
|
}
|
|
else
|
|
{
|
|
if (activeWorkAreas.contains(monitor))
|
|
{
|
|
const auto& workArea = activeWorkAreas.at(monitor);
|
|
const auto& layout = workArea->GetLayout();
|
|
if (layout)
|
|
{
|
|
const auto& zones = layout->Zones();
|
|
for (const auto& [zoneId, zone] : zones)
|
|
{
|
|
RECT zoneRect = zone.GetZoneRect();
|
|
|
|
zoneRect.left += monitorRect.left;
|
|
zoneRect.right += monitorRect.left;
|
|
zoneRect.top += monitorRect.top;
|
|
zoneRect.bottom += monitorRect.top;
|
|
|
|
zoneRects.emplace_back(zoneRect);
|
|
zoneRectsInfo.emplace_back(zoneId, workArea.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
|
|
|
|
if (chosenIdx < zoneRects.size())
|
|
{
|
|
// Moving to another monitor succeeded
|
|
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
|
|
bool snapped = false;
|
|
if (workArea)
|
|
{
|
|
snapped = workArea->Snap(window, { trueZoneIdx });
|
|
}
|
|
|
|
if (snapped)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
|
|
}
|
|
|
|
return snapped;
|
|
}
|
|
|
|
// We reached the end of all monitors.
|
|
// Try again, cycling on all monitors.
|
|
// First, add zones from the origin monitor to zoneRects
|
|
// Sanity check: the current monitor is valid
|
|
if (currentMonitorRect.top <= currentMonitorRect.bottom)
|
|
{
|
|
const auto& currentWorkArea = activeWorkAreas.at(current);
|
|
if (currentWorkArea)
|
|
{
|
|
const auto& layout = currentWorkArea->GetLayout();
|
|
if (layout)
|
|
{
|
|
const auto& zones = layout->Zones();
|
|
for (const auto& [zoneId, zone] : zones)
|
|
{
|
|
RECT zoneRect = zone.GetZoneRect();
|
|
|
|
zoneRect.left += currentMonitorRect.left;
|
|
zoneRect.right += currentMonitorRect.left;
|
|
zoneRect.top += currentMonitorRect.top;
|
|
zoneRect.bottom += currentMonitorRect.top;
|
|
|
|
zoneRects.emplace_back(zoneRect);
|
|
zoneRectsInfo.emplace_back(zoneId, currentWorkArea.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
RECT combinedRect = FancyZonesUtils::GetMonitorsCombinedRect<&MONITORINFOEX::rcWork>(monitors);
|
|
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
|
|
chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
|
|
if (chosenIdx < zoneRects.size())
|
|
{
|
|
// Moving to another monitor succeeded
|
|
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
|
|
|
|
bool snapped = false;
|
|
if (workArea)
|
|
{
|
|
snapped = workArea->Snap(window, { trueZoneIdx });
|
|
}
|
|
|
|
if (snapped)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
|
|
}
|
|
|
|
return snapped;
|
|
}
|
|
else
|
|
{
|
|
// Giving up
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool WindowKeyboardSnap::MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea)
|
|
{
|
|
if (!workArea)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto& layout = workArea->GetLayout();
|
|
const auto& zones = layout->Zones();
|
|
const auto& layoutWindows = workArea->GetLayoutWindows();
|
|
if (!layout || zones.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto zoneIndexes = layoutWindows.GetZoneIndexSetFromWindow(window);
|
|
const auto numZones = zones.size();
|
|
bool snapped = false;
|
|
|
|
// The window was not assigned to any zone here
|
|
if (zoneIndexes.size() == 0)
|
|
{
|
|
const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
|
|
snapped = workArea->Snap(window, { zone });
|
|
}
|
|
else
|
|
{
|
|
const ZoneIndex oldId = zoneIndexes[0];
|
|
|
|
// We reached the edge
|
|
if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast<int64_t>(numZones) - 1))
|
|
{
|
|
if (!cycle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
|
|
snapped = workArea->Snap(window, { zone });
|
|
}
|
|
else
|
|
{
|
|
// We didn't reach the edge
|
|
if (vkCode == VK_LEFT)
|
|
{
|
|
snapped = workArea->Snap(window, { oldId - 1 });
|
|
}
|
|
else
|
|
{
|
|
snapped = workArea->Snap(window, { oldId + 1 });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (snapped)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
|
|
}
|
|
|
|
return snapped;
|
|
}
|
|
|
|
bool WindowKeyboardSnap::MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea)
|
|
{
|
|
if (!workArea)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto& layout = workArea->GetLayout();
|
|
const auto& zones = layout->Zones();
|
|
const auto& layoutWindows = workArea->GetLayoutWindows();
|
|
if (!layout || zones.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::vector<bool> usedZoneIndices(zones.size(), false);
|
|
auto windowZones = layoutWindows.GetZoneIndexSetFromWindow(window);
|
|
|
|
for (const ZoneIndex id : windowZones)
|
|
{
|
|
usedZoneIndices[id] = true;
|
|
}
|
|
|
|
std::vector<RECT> zoneRects;
|
|
ZoneIndexSet freeZoneIndices;
|
|
|
|
for (const auto& [zoneId, zone] : zones)
|
|
{
|
|
if (!usedZoneIndices[zoneId])
|
|
{
|
|
zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
|
|
freeZoneIndices.emplace_back(zoneId);
|
|
}
|
|
}
|
|
|
|
// Move to coordinates relative to windowZone
|
|
const auto& workAreaRect = workArea->GetWorkAreaRect();
|
|
windowRect.top -= workAreaRect.top();
|
|
windowRect.bottom -= workAreaRect.top();
|
|
windowRect.left -= workAreaRect.left();
|
|
windowRect.right -= workAreaRect.left();
|
|
|
|
ZoneIndex result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
|
|
if (static_cast<size_t>(result) < zoneRects.size())
|
|
{
|
|
bool success = workArea->Snap(window, { freeZoneIndices[result] });
|
|
if (success)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
|
|
}
|
|
return success;
|
|
}
|
|
else if (cycle)
|
|
{
|
|
// Try again from the position off the screen in the opposite direction to vkCode
|
|
// Consider all zones as available
|
|
zoneRects.resize(zones.size());
|
|
std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); });
|
|
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(workAreaRect.left(), workAreaRect.top(), workAreaRect.right(), workAreaRect.bottom()), vkCode);
|
|
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
|
|
|
|
if (static_cast<size_t>(result) < zoneRects.size())
|
|
{
|
|
bool success = workArea->Snap(window, { result });
|
|
|
|
if (success)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea)
|
|
{
|
|
if (!workArea)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto& layout = workArea->GetLayout();
|
|
const auto& layoutWindows = workArea->GetLayoutWindows();
|
|
if (!layout || layout->Zones().empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto& zones = layout->Zones();
|
|
auto appliedZones = layoutWindows.GetZoneIndexSetFromWindow(window);
|
|
|
|
std::vector<bool> usedZoneIndices(zones.size(), false);
|
|
std::vector<RECT> zoneRects;
|
|
ZoneIndexSet freeZoneIndices;
|
|
|
|
// If selectManyZones = true for the second time, use the last zone into which we moved
|
|
// instead of the window rect and enable moving to all zones except the old one
|
|
if (m_extendData.IsExtended(window))
|
|
{
|
|
usedZoneIndices[m_extendData.windowFinalIndex] = true;
|
|
windowRect = zones.at(m_extendData.windowFinalIndex).GetZoneRect();
|
|
}
|
|
else
|
|
{
|
|
for (const ZoneIndex idx : appliedZones)
|
|
{
|
|
usedZoneIndices[idx] = true;
|
|
}
|
|
|
|
// Move to coordinates relative to windowZone
|
|
const auto& workAreaRect = workArea->GetWorkAreaRect();
|
|
windowRect.top -= workAreaRect.top();
|
|
windowRect.bottom -= workAreaRect.top();
|
|
windowRect.left -= workAreaRect.left();
|
|
windowRect.right -= workAreaRect.left();
|
|
|
|
m_extendData.Set(window);
|
|
}
|
|
|
|
for (size_t i = 0; i < zones.size(); i++)
|
|
{
|
|
if (!usedZoneIndices[i])
|
|
{
|
|
zoneRects.emplace_back(zones.at(i).GetZoneRect());
|
|
freeZoneIndices.emplace_back(i);
|
|
}
|
|
}
|
|
|
|
const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
|
|
if (result >= zoneRects.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ZoneIndex targetZone = freeZoneIndices[result];
|
|
ZoneIndexSet resultIndexSet;
|
|
|
|
// First time with selectManyZones = true for this window?
|
|
if (m_extendData.windowFinalIndex == -1)
|
|
{
|
|
// Already zoned?
|
|
if (appliedZones.size())
|
|
{
|
|
m_extendData.windowInitialIndexSet = appliedZones;
|
|
m_extendData.windowFinalIndex = targetZone;
|
|
resultIndexSet = layout->GetCombinedZoneRange(appliedZones, { targetZone });
|
|
}
|
|
else
|
|
{
|
|
m_extendData.windowInitialIndexSet = { targetZone };
|
|
m_extendData.windowFinalIndex = targetZone;
|
|
resultIndexSet = { targetZone };
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto deletethis = m_extendData.windowInitialIndexSet;
|
|
m_extendData.windowFinalIndex = targetZone;
|
|
resultIndexSet = layout->GetCombinedZoneRange(m_extendData.windowInitialIndexSet, { targetZone });
|
|
}
|
|
|
|
bool success = workArea->Snap(window, resultIndexSet);
|
|
if (success)
|
|
{
|
|
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
|
|
}
|
|
|
|
return success;
|
|
}
|