mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
Windows snap hotkeys to move windows between screens (#1603)
* When moving window into zones using arrow keys, support multi-monitor scenario * Minor coding style adjustments * Split implementation into separate functions because of readability * Rename certain arguments * Modify unit tests after API changes * Address PR comments and add unit tests * Return true from MoveWindowIntoZoneByDirection only if window is successfully added to new zone * Improved monitor ordering (#1) * Implemented improved monitor ordering v1 * Fixed some embarrassing bugs, added some tests * Added one more test * Extracted a value to a variable * ASCII art in unit test comments describing monitor layouts * Removed empty line for consistency * Update comment to match the code * Refactored tests, added tests for X,Y offsets Co-authored-by: Ivan Stošić <ivan100sic@gmail.com>
This commit is contained in:
@@ -1,32 +1,235 @@
|
||||
#include "pch.h"
|
||||
#include "Util.h"
|
||||
#include "lib\util.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace FancyZonesUnitTests
|
||||
{
|
||||
TEST_CLASS(UtilUnitTests){
|
||||
public:
|
||||
TEST_METHOD(TestParseDeviceId){
|
||||
// We're interested in the unique part between the first and last #'s
|
||||
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// Example output: DELA026#5&10a58c63&0&UID16777488
|
||||
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
|
||||
wchar_t output[256]{};
|
||||
ParseDeviceId(input, output, ARRAYSIZE(output));
|
||||
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
|
||||
void TestMonitorSetPermutations(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
|
||||
{
|
||||
auto monitorInfoPermutation = monitorInfo;
|
||||
|
||||
do {
|
||||
auto monitorInfoCopy = monitorInfoPermutation;
|
||||
OrderMonitors(monitorInfoCopy);
|
||||
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
|
||||
} while (std::next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
|
||||
}
|
||||
|
||||
void TestMonitorSetPermutationsOffsets(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
|
||||
{
|
||||
for (int offsetX = -3000; offsetX <= 3000; offsetX += 1000)
|
||||
{
|
||||
for (int offsetY = -3000; offsetY <= 3000; offsetY += 1000)
|
||||
{
|
||||
auto monitorInfoCopy = monitorInfo;
|
||||
for (auto& [monitor, rect] : monitorInfoCopy)
|
||||
{
|
||||
rect.left += offsetX;
|
||||
rect.right += offsetX;
|
||||
rect.top += offsetY;
|
||||
rect.bottom += offsetY;
|
||||
}
|
||||
TestMonitorSetPermutations(monitorInfoCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CLASS(UtilUnitTests)
|
||||
{
|
||||
TEST_METHOD(TestParseDeviceId)
|
||||
{
|
||||
// We're interested in the unique part between the first and last #'s
|
||||
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// Example output: DELA026#5&10a58c63&0&UID16777488
|
||||
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
|
||||
wchar_t output[256]{};
|
||||
ParseDeviceId(input, output, ARRAYSIZE(output));
|
||||
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
|
||||
}
|
||||
|
||||
TEST_METHOD(TestParseInvalidDeviceId)
|
||||
{
|
||||
// We're interested in the unique part between the first and last #'s
|
||||
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// Example output: DELA026#5&10a58c63&0&UID16777488
|
||||
PCWSTR input = L"AnInvalidDeviceId";
|
||||
wchar_t output[256]{};
|
||||
ParseDeviceId(input, output, ARRAYSIZE(output));
|
||||
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering01)
|
||||
{
|
||||
// Three horizontally arranged monitors, bottom aligned, with increasing sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 200, .right = 1600, .bottom = 1100} },
|
||||
{Mocks::Monitor(), RECT{.left = 1600, .top = 100, .right = 3300, .bottom = 1100} },
|
||||
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering02)
|
||||
{
|
||||
// Three horizontally arranged monitors, bottom aligned, with equal sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering03)
|
||||
{
|
||||
// Three horizontally arranged monitors, bottom aligned, with decreasing sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
|
||||
{Mocks::Monitor(), RECT{.left = 1800, .top = 100, .right = 3500, .bottom = 1100} },
|
||||
{Mocks::Monitor(), RECT{.left = 3500, .top = 200, .right = 5100, .bottom = 1100} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering04)
|
||||
{
|
||||
// Three horizontally arranged monitors, top aligned, with increasing sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3300, .bottom = 1000} },
|
||||
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering05)
|
||||
{
|
||||
// Three horizontally arranged monitors, top aligned, with equal sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering06)
|
||||
{
|
||||
// Three horizontally arranged monitors, top aligned, with decreasing sizes
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
|
||||
{Mocks::Monitor(), RECT{.left = 1800, .top = 0, .right = 3500, .bottom = 1000} },
|
||||
{Mocks::Monitor(), RECT{.left = 3500, .top = 0, .right = 5100, .bottom = 900} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering07)
|
||||
{
|
||||
// Three vertically arranged monitors, center aligned, with equal sizes, except the middle monitor is a bit wider
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 100, .top = 0, .right = 1700, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 900, .right = 1800, .bottom = 1800} },
|
||||
{Mocks::Monitor(), RECT{.left = 100, .top = 1800, .right = 1700, .bottom = 2700} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering08)
|
||||
{
|
||||
// ------------------
|
||||
// | || || |
|
||||
// | || || |
|
||||
// ------------------
|
||||
// | || |
|
||||
// | || |
|
||||
// ------------------
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 600, .bottom = 400} },
|
||||
{Mocks::Monitor(), RECT{.left = 600, .top = 0, .right = 1200, .bottom = 400} },
|
||||
{Mocks::Monitor(), RECT{.left = 1200, .top = 0, .right = 1800, .bottom = 400} },
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 900, .bottom = 800} },
|
||||
{Mocks::Monitor(), RECT{.left = 900, .top = 400, .right = 1800, .bottom = 800} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering09)
|
||||
{
|
||||
// Regular 3x3 grid
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 400, .bottom = 300} },
|
||||
{Mocks::Monitor(), RECT{.left = 400, .top = 0, .right = 800, .bottom = 300} },
|
||||
{Mocks::Monitor(), RECT{.left = 800, .top = 0, .right = 1200, .bottom = 300} },
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 300, .right = 400, .bottom = 600} },
|
||||
{Mocks::Monitor(), RECT{.left = 400, .top = 300, .right = 800, .bottom = 600} },
|
||||
{Mocks::Monitor(), RECT{.left = 800, .top = 300, .right = 1200, .bottom = 600} },
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 600, .right = 400, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 400, .top = 600, .right = 800, .bottom = 900} },
|
||||
{Mocks::Monitor(), RECT{.left = 800, .top = 600, .right = 1200, .bottom = 900} },
|
||||
};
|
||||
|
||||
// Reduce running time by testing only rotations
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
auto monitorInfoCopy = monitorInfo;
|
||||
std::rotate(monitorInfoCopy.begin(), monitorInfoCopy.begin() + i, monitorInfoCopy.end());
|
||||
OrderMonitors(monitorInfoCopy);
|
||||
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering10)
|
||||
{
|
||||
// ------------------
|
||||
// | || |
|
||||
// | || |
|
||||
// ------------------
|
||||
// | || || |
|
||||
// | || || |
|
||||
// ------------------
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 900, .bottom = 400} },
|
||||
{Mocks::Monitor(), RECT{.left = 900, .top = 0, .right = 1800, .bottom = 400} },
|
||||
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 600, .bottom = 800} },
|
||||
{Mocks::Monitor(), RECT{.left = 600, .top = 400, .right = 1200, .bottom = 800} },
|
||||
{Mocks::Monitor(), RECT{.left = 1200, .top = 400, .right = 1800, .bottom = 800} },
|
||||
};
|
||||
|
||||
TestMonitorSetPermutationsOffsets(monitorInfo);
|
||||
}
|
||||
|
||||
TEST_METHOD(TestMonitorOrdering11)
|
||||
{
|
||||
// Random values, some monitors overlap, don't check order, just ensure it doesn't crash and it's the same every time
|
||||
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
|
||||
{Mocks::Monitor(), RECT{.left = 410, .top = 630, .right = 988, .bottom = 631} },
|
||||
{Mocks::Monitor(), RECT{.left = 302, .top = 189, .right = 550, .bottom = 714} },
|
||||
{Mocks::Monitor(), RECT{.left = 158, .top = 115, .right = 657, .bottom = 499} },
|
||||
{Mocks::Monitor(), RECT{.left = 341, .top = 340, .right = 723, .bottom = 655} },
|
||||
{Mocks::Monitor(), RECT{.left = 433, .top = 393, .right = 846, .bottom = 544} },
|
||||
};
|
||||
|
||||
auto monitorInfoPermutation = monitorInfo;
|
||||
auto firstTime = monitorInfo;
|
||||
OrderMonitors(firstTime);
|
||||
|
||||
do {
|
||||
auto monitorInfoCopy = monitorInfoPermutation;
|
||||
OrderMonitors(monitorInfoCopy);
|
||||
CustomAssert::AreEqual(firstTime, monitorInfoCopy);
|
||||
} while (next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TEST_METHOD(TestParseInvalidDeviceId)
|
||||
{
|
||||
// We're interested in the unique part between the first and last #'s
|
||||
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||
// Example output: DELA026#5&10a58c63&0&UID16777488
|
||||
PCWSTR input = L"AnInvalidDeviceId";
|
||||
wchar_t output[256]{};
|
||||
ParseDeviceId(input, output, ARRAYSIZE(output));
|
||||
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ namespace CustomAssert
|
||||
{
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2);
|
||||
}
|
||||
|
||||
static void AreEqual(const std::vector<std::pair<HMONITOR, RECT>>& a1, const std::vector<std::pair<HMONITOR, RECT>>& a2)
|
||||
{
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1.size() == a2.size());
|
||||
for (size_t i = 0; i < a1.size(); i++)
|
||||
{
|
||||
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Mocks
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -523,7 +523,7 @@ namespace FancyZonesUnitTests
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
|
||||
@@ -537,9 +537,9 @@ namespace FancyZonesUnitTests
|
||||
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
|
||||
|
||||
const auto window = Mocks::WindowCreate(m_hInst);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
|
||||
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
|
||||
|
||||
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
|
||||
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
|
||||
|
||||
Reference in New Issue
Block a user