From 6acae53e2ccea1efbabcce5e7c3ec1e5acf5c1f4 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Tue, 22 Aug 2023 15:57:45 +0300 Subject: [PATCH] [FancyZones] Improve code quality (part 6) (#28034) --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 368 +---- .../FancyZonesData/AppZoneHistory.cpp | 131 +- .../FancyZonesData/AppZoneHistory.h | 6 +- .../FancyZonesLib/FancyZonesDataTypes.h | 6 +- .../FancyZonesLib/FancyZonesLib.vcxproj | 6 +- .../FancyZonesLib.vcxproj.filters | 10 +- .../FancyZonesWindowProperties.cpp | 6 +- .../FancyZonesWindowProperties.h | 2 +- .../fancyzones/FancyZonesLib/JsonHelpers.cpp | 7 +- .../FancyZonesLib/LayoutAssignedWindows.cpp | 34 +- .../FancyZonesLib/LayoutAssignedWindows.h | 13 +- .../fancyzones/FancyZonesLib/Settings.h | 7 + .../FancyZonesLib/WindowKeyboardSnap.cpp | 489 ++++++ .../FancyZonesLib/WindowKeyboardSnap.h | 56 + .../FancyZonesLib/WindowMouseSnap.cpp | 244 +++ .../{WindowDrag.h => WindowMouseSnap.h} | 8 +- .../fancyzones/FancyZonesLib/WindowUtils.cpp | 25 +- .../fancyzones/FancyZonesLib/WorkArea.cpp | 379 +---- .../fancyzones/FancyZonesLib/WorkArea.h | 27 +- .../fancyzones/FancyZonesLib/trace.cpp | 16 +- src/modules/fancyzones/FancyZonesLib/trace.h | 10 +- src/modules/fancyzones/FancyZonesLib/util.cpp | 9 + src/modules/fancyzones/FancyZonesLib/util.h | 13 +- .../UnitTests/AppZoneHistoryTests.Spec.cpp | 64 +- .../FancyZonesTests/UnitTests/Layout.Spec.cpp | 14 +- .../UnitTests/UnitTests.vcxproj | 1 + .../UnitTests/UnitTests.vcxproj.filters | 3 + .../UnitTests/WindowKeyboardSnap.Spec.cpp | 1452 +++++++++++++++++ .../UnitTests/WorkArea.Spec.cpp | 502 +----- 29 files changed, 2669 insertions(+), 1239 deletions(-) create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp rename src/modules/fancyzones/FancyZonesLib/{WindowDrag.h => WindowMouseSnap.h} (82%) create mode 100644 src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index caa77429de..d62c1d0cc6 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -26,7 +26,8 @@ #include #include #include -#include +#include +#include #include enum class DisplayChangeType @@ -142,10 +143,6 @@ protected: private: void UpdateWorkAreas(bool updateWindowPositions) noexcept; void CycleWindows(bool reverse) noexcept; - bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkey(DWORD vkCode) noexcept; - bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept; void SyncVirtualDesktops() noexcept; @@ -153,13 +150,11 @@ private: bool MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept; - void UpdateActiveLayouts() noexcept; + void RefreshLayouts() noexcept; bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept; void ApplyQuickLayout(int key) noexcept; void FlashZones() noexcept; - std::vector> GetRawMonitorData() noexcept; - std::vector GetMonitorsSorted() noexcept; HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; virtual void SettingsUpdate(SettingId type) override; @@ -167,7 +162,8 @@ private: const HINSTANCE m_hinstance{}; HWND m_window{}; - std::unique_ptr m_windowDrag{}; + std::unique_ptr m_windowMouseSnapper{}; + WindowKeyboardSnap m_windowKeyboardSnapper{}; MonitorWorkAreaMap m_workAreaHandler; DraggingState m_draggingState; @@ -288,8 +284,8 @@ FancyZones::VirtualDesktopChanged() noexcept void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) { - m_windowDrag = WindowDrag::Create(window, m_workAreaHandler.GetAllWorkAreas()); - if (m_windowDrag) + m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas()); + if (m_windowMouseSnapper) { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) { @@ -298,13 +294,13 @@ void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) m_draggingState.Enable(); m_draggingState.UpdateDraggingState(); - m_windowDrag->MoveSizeStart(monitor, m_draggingState.IsDragging()); + m_windowMouseSnapper->MoveSizeStart(monitor, m_draggingState.IsDragging()); } } void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) { - if (m_windowDrag) + if (m_windowMouseSnapper) { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) { @@ -312,17 +308,17 @@ void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) } m_draggingState.UpdateDraggingState(); - m_windowDrag->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState()); + m_windowMouseSnapper->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState()); } } void FancyZones::MoveSizeEnd() { - if (m_windowDrag) + if (m_windowMouseSnapper) { - m_windowDrag->MoveSizeEnd(); + m_windowMouseSnapper->MoveSizeEnd(); m_draggingState.Disable(); - m_windowDrag = nullptr; + m_windowMouseSnapper = nullptr; } } @@ -339,7 +335,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept workArea = workAreas.at(monitor).get(); if (workArea) { - indexes = workArea->GetWindowZoneIndexes(window); + indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); } } else @@ -353,7 +349,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept { if (secondaryWorkArea) { - indexes = secondaryWorkArea->GetWindowZoneIndexes(window); + indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, secondaryWorkArea->UniqueId(), secondaryWorkArea->GetLayoutId()); workArea = secondaryWorkArea.get(); if (!indexes.empty()) { @@ -365,8 +361,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept if (!indexes.empty() && workArea) { - Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - workArea->MoveWindowIntoZoneByIndexSet(window, indexes); + Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + workArea->Snap(window, indexes); return true; } @@ -604,7 +600,40 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa if (message == WM_PRIV_SNAP_HOTKEY) { - OnSnapHotkey(static_cast(lparam)); + // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning + auto foregroundWindow = GetForegroundWindow(); + + HMONITOR monitor{ nullptr }; + if (!FancyZonesSettings::settings().spanZonesAcrossMonitors) + { + monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTONULL); + } + + if (FancyZonesSettings::settings().moveWindowsBasedOnPosition) + { + auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); + RECT windowRect; + if (GetWindowRect(foregroundWindow, &windowRect)) + { + // Check whether Alt is used in the shortcut key combination + if (GetAsyncKeyState(VK_MENU) & 0x8000) + { + m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas()); + } + else + { + m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors); + } + } + else + { + Logger::error("Error snapping window by keyboard shortcut: failed to get window rect"); + } + } + else + { + m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered()); + } } else if (message == WM_PRIV_INIT) { @@ -661,7 +690,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa else if (message == WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE) { AppliedLayouts::instance().LoadData(); - UpdateActiveLayouts(); + RefreshLayouts(); } else if (message == WM_PRIV_DEFAULT_LAYOUTS_FILE_UPDATE) { @@ -800,7 +829,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept { for (const auto& [window, zones] : windowsToSnap) { - workArea->SnapWindow(window, zones, false); + workArea->Snap(window, zones, false); } } } @@ -813,9 +842,9 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept const auto zones = iter->second; const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor); - if (workAreaForMonitor && workAreaForMonitor->GetWindowZoneIndexes(window) == zones) + if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones) { - workAreaForMonitor->SnapWindow(window, zones, false); + workAreaForMonitor->Snap(window, zones, false); iter = windowsToSnap.erase(iter); } else @@ -829,10 +858,10 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept { for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) { - const auto savedIndexes = workArea->GetWindowZoneIndexes(window); + const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); if (savedIndexes == zones) { - workArea->SnapWindow(window, zones, false); + workArea->Snap(window, zones, false); } } } @@ -862,257 +891,6 @@ void FancyZones::CycleWindows(bool reverse) noexcept } } -bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept -{ - HMONITOR current = WorkAreaKeyFromWindow(window); - - std::vector monitorInfo = GetMonitorsSorted(); - if (current && monitorInfo.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) - { - // Multi monitor environment. - auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); - do - { - auto workArea = m_workAreaHandler.GetWorkArea(*currMonitorInfo); - if (workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */)) - { - // unassign from previous work area - for (auto& [_, prevWorkArea] : m_workAreaHandler.GetAllWorkAreas()) - { - if (prevWorkArea && workArea != prevWorkArea.get()) - { - prevWorkArea->UnsnapWindow(window); - } - } - - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - 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) - { - currMonitorInfo = std::next(currMonitorInfo); - if (currMonitorInfo == std::end(monitorInfo)) - { - currMonitorInfo = std::begin(monitorInfo); - } - } - else if (vkCode == VK_LEFT) - { - if (currMonitorInfo == std::begin(monitorInfo)) - { - currMonitorInfo = std::end(monitorInfo); - } - currMonitorInfo = std::prev(currMonitorInfo); - } - } while (*currMonitorInfo != current); - } - else - { - auto workArea = m_workAreaHandler.GetWorkArea(current); - // Single monitor environment, or combined multi-monitor environment. - if (FancyZonesSettings::settings().restoreSize) - { - bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */); - if (!moved) - { - FancyZonesWindowUtils::RestoreWindowOrigin(window); - FancyZonesWindowUtils::RestoreWindowSize(window); - } - else if (workArea) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return moved; - } - else - { - bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */); - - if (moved) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return moved; - } - } - - return false; -} - -bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept -{ - HMONITOR current = WorkAreaKeyFromWindow(window); - - auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); - - if (current && allMonitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) - { - // Multi monitor environment. - // First, try to stay on the same monitor - bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(current)); - if (success) - { - return true; - } - - // If that didn't work, extract zones from all other monitors and target one of them - std::vector zoneRects; - std::vector> zoneRectsInfo; - RECT currentMonitorRect{ .top = 0, .bottom = -1 }; - - for (const auto& [monitor, monitorRect] : allMonitors) - { - if (monitor == current) - { - currentMonitorRect = monitorRect; - } - else - { - auto workArea = m_workAreaHandler.GetWorkArea(monitor); - if (workArea) - { - 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); - } - } - } - } - } - - // Ensure we can get the windowRect, if not, just quit - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - return false; - } - - auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (chosenIdx < zoneRects.size()) - { - // Moving to another monitor succeeded - const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; - if (workArea) - { - workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return true; - } - - // 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) - { - auto workArea = m_workAreaHandler.GetWorkArea(current); - if (workArea) - { - const auto& layout = workArea->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, workArea); - } - } - } - } - else - { - return false; - } - - RECT combinedRect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>(); - 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]; - - if (workArea) - { - workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return true; - } - else - { - // Giving up - return false; - } - } - else - { - // Single monitor environment, or combined multi-monitor environment. - return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(current)); - } -} - -bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept -{ - // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning - auto window = GetForegroundWindow(); - if (FancyZonesSettings::settings().moveWindowsBasedOnPosition) - { - return OnSnapHotkeyBasedOnPosition(window, vkCode); - } - - return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode); -} - -bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept -{ - // Check whether Alt is used in the shortcut key combination - if (GetAsyncKeyState(VK_MENU) & 0x8000) - { - bool result = workArea && workArea->ExtendWindowByDirectionAndPosition(window, vkCode); - if (result) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return result; - } - else - { - bool result = workArea && workArea->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); - if (result) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return result; - } -} - void FancyZones::SyncVirtualDesktops() noexcept { auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry(); @@ -1186,13 +964,13 @@ void FancyZones::SettingsUpdate(SettingId id) } } -void FancyZones::UpdateActiveLayouts() noexcept +void FancyZones::RefreshLayouts() noexcept { for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) { if (workArea) { - workArea->UpdateActiveZoneSet(); + workArea->InitLayout(); if (FancyZonesSettings::settings().zoneSetChange_moveWindows) { @@ -1265,7 +1043,7 @@ void FancyZones::ApplyQuickLayout(int key) noexcept { AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value()); AppliedLayouts::instance().SaveData(); - UpdateActiveLayouts(); + RefreshLayouts(); FlashZones(); } } @@ -1284,32 +1062,6 @@ void FancyZones::FlashZones() noexcept } } -std::vector FancyZones::GetMonitorsSorted() noexcept -{ - auto monitorInfo = GetRawMonitorData(); - FancyZonesUtils::OrderMonitors(monitorInfo); - std::vector output; - std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; }); - return output; -} - -std::vector> FancyZones::GetRawMonitorData() noexcept -{ - std::vector> monitorInfo; - const auto& activeWorkAreaMap = m_workAreaHandler.GetAllWorkAreas(); - for (const auto& [monitor, workArea] : activeWorkAreaMap) - { - if (workArea && workArea->GetLayout() != nullptr) - { - MONITORINFOEX mi; - mi.cbSize = sizeof(mi); - GetMonitorInfo(monitor, &mi); - monitorInfo.push_back({ monitor, mi.rcMonitor }); - } - } - return monitorInfo; -} - HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp index 6eaf81d90d..886dec5a84 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp @@ -103,13 +103,14 @@ namespace JsonUtils } data.workAreaId = deviceIdOpt.value(); - data.zoneSetUuid = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID); - - if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid)) + std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID).c_str(); + auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr); + if (!layoutIdOpt.has_value()) { return std::nullopt; } + data.layoutId = layoutIdOpt.value(); return data; } @@ -187,7 +188,11 @@ namespace JsonUtils desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIndexesID, jsonIndexSet); desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::DeviceID, device); - desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(data.zoneSetUuid)); + auto layoutIdStr = FancyZonesUtils::GuidToString(data.layoutId); + if (layoutIdStr) + { + desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(layoutIdStr.value())); + } appHistoryArray.Append(desktopData); } @@ -334,7 +339,7 @@ void AppZoneHistory::AdjustWorkAreaIds(const std::vector processIdToHandleMap{}; processIdToHandleMap[processId] = window; FancyZonesDataTypes::AppZoneHistoryData data{ .processIdToHandleMap = processIdToHandleMap, - .zoneSetUuid = zoneSetId, + .layoutId = layoutId, .workAreaId = workAreaId, .zoneIndexSet = zoneIndexSet }; @@ -392,56 +401,66 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor return true; } -bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) +bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) { - Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), zoneSetId); - auto processPath = get_process_path_waiting_uwp(window); - if (!processPath.empty()) + if (processPath.empty()) { - auto history = m_history.find(processPath); - if (history != std::end(m_history)) - { - auto& perDesktopData = history->second; - for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);) - { - if (data->workAreaId == workAreaId && data->zoneSetUuid == zoneSetId) - { - if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId)) - { - DWORD processId = 0; - GetWindowThreadProcessId(window, &processId); - - data->processIdToHandleMap.erase(processId); - } - - // if there is another instance of same application placed in the same zone don't erase history - auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); - for (auto placedWindow : data->processIdToHandleMap) - { - auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second); - if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps)) - { - return false; - } - } - - data = perDesktopData.erase(data); - if (perDesktopData.empty()) - { - m_history.erase(processPath); - } - SaveData(); - return true; - } - else - { - ++data; - } - } - } + return false; } + auto history = m_history.find(processPath); + if (history == std::end(m_history)) + { + return false; + } + + auto layoutIdStrOpt = FancyZonesUtils::GuidToString(layoutId); + if (!layoutIdStrOpt) + { + Logger::error("Invalid layout id"); + return false; + } + + Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), layoutIdStrOpt.value()); + + auto& perDesktopData = history->second; + for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);) + { + if (data->workAreaId == workAreaId && data->layoutId == layoutId) + { + if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId)) + { + DWORD processId = 0; + GetWindowThreadProcessId(window, &processId); + + data->processIdToHandleMap.erase(processId); + } + + // if there is another instance of same application placed in the same zone don't erase history + auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); + for (auto placedWindow : data->processIdToHandleMap) + { + auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second); + if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps)) + { + return false; + } + } + + data = perDesktopData.erase(data); + if (perDesktopData.empty()) + { + m_history.erase(processPath); + } + SaveData(); + return true; + } + else + { + ++data; + } + } return false; } @@ -532,7 +551,7 @@ bool AppZoneHistory::IsAnotherWindowOfApplicationInstanceZoned(HWND window, cons return false; } -ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const +ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const { auto processPath = get_process_path_waiting_uwp(window); if (processPath.empty()) @@ -559,7 +578,7 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone const auto& perDesktopData = history->second; for (const auto& data : perDesktopData) { - if (data.zoneSetUuid == zoneSetId && data.workAreaId == workAreaId) + if (data.layoutId == layoutId && data.workAreaId == workAreaId) { if (data.workAreaId.virtualDesktopId == workAreaId.virtualDesktopId || data.workAreaId.virtualDesktopId == GUID_NULL) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h index 8c69cc5b5a..18ec84ac93 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h @@ -45,8 +45,8 @@ public: void SaveData(); void AdjustWorkAreaIds(const std::vector& ids); - bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet); - bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId); + bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId, const ZoneIndexSet& zoneIndexSet); + bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId); void RemoveApp(const std::wstring& appPath); @@ -54,7 +54,7 @@ public: std::optional GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; - ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const; + ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const; void SyncVirtualDesktops(); void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h index 1770f0f646..6f61b3e125 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h @@ -144,9 +144,9 @@ namespace FancyZonesDataTypes { std::unordered_map processIdToHandleMap; // Maps process id(DWORD) of application to zoned window handle(HWND) - std::wstring zoneSetUuid; - WorkAreaId workAreaId; - ZoneIndexSet zoneIndexSet; + GUID layoutId = {}; + WorkAreaId workAreaId = {}; + ZoneIndexSet zoneIndexSet = {}; }; struct DeviceInfoData diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index ad923bfcb9..bd79059fb7 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -71,7 +71,8 @@ - + + @@ -123,7 +124,8 @@ - + + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index f34893d20d..e8012868f2 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -162,7 +162,10 @@ Header Files - + + Header Files + + Header Files @@ -263,7 +266,10 @@ Source Files - + + Source Files + + Source Files diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp index dd3298cd8a..306b4ccab0 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp @@ -15,7 +15,7 @@ namespace ZonedWindowProperties const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone"; } -void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet) +bool FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet) { RemoveZoneIndexProperty(window); ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(zoneSet); @@ -33,6 +33,7 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone64ID, rawData)) { Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError())); + return false; } } @@ -49,8 +50,11 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone128ID, rawData)) { Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError())); + return false; } } + + return true; } void FancyZonesWindowProperties::RemoveZoneIndexProperty(HWND window) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h index 8b3bae38e1..0bd8ff16bf 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h @@ -18,7 +18,7 @@ namespace ZonedWindowProperties namespace FancyZonesWindowProperties { - void StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet); + bool StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet); void RemoveZoneIndexProperty(HWND window); ZoneIndexSet RetrieveZoneIndexProperty(HWND window); diff --git a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp index c557bcf1a3..44bfb5fec9 100644 --- a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp +++ b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp @@ -277,13 +277,16 @@ namespace .monitorId = { .deviceId = MonitorUtils::Display::ConvertObsoleteDeviceId(deviceId->deviceName) }, .virtualDesktopId = deviceId->virtualDesktopId }; - data.zoneSetUuid = json.GetNamedString(NonLocalizable::ZoneSetUuidStr); - if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid)) + std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::ZoneSetUuidStr).c_str(); + auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr); + if (!layoutIdOpt.has_value()) { return std::nullopt; } + data.layoutId = layoutIdOpt.value(); + return data; } diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp index 75cc6b5fa4..f2089cf176 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp @@ -6,37 +6,10 @@ #include #include -LayoutAssignedWindows::LayoutAssignedWindows() -{ - m_extendData = std::make_unique(); -} - void LayoutAssignedWindows::Assign(HWND window, const ZoneIndexSet& zones) { Dismiss(window); - - // clear info about extension - std::erase_if(m_extendData->windowInitialIndexSet, [window](const auto& item) { return item.first == window; }); - std::erase_if(m_extendData->windowFinalIndex, [window](const auto& item) { return item.first == window; }); - - for (const auto& index : zones) - { - m_windowIndexSet[window].push_back(index); - } - - if (FancyZonesSettings::settings().disableRoundCorners) - { - FancyZonesWindowUtils::DisableRoundCorners(window); - } - - auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window); - InsertWindowIntoZone(window, tabSortKeyWithinZone, zones); -} - -void LayoutAssignedWindows::Extend(HWND window, const ZoneIndexSet& zones) -{ - Dismiss(window); - + for (const auto& index : zones) { m_windowIndexSet[window].push_back(index); @@ -133,11 +106,6 @@ void LayoutAssignedWindows::CycleWindows(HWND window, bool reverse) } } -const std::unique_ptr& LayoutAssignedWindows::ExtendWindowData() -{ - return m_extendData; -} - void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) { if (tabSortKeyWithinZone.has_value()) diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h index 64377e36b1..b7754b057d 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h @@ -4,19 +4,11 @@ class LayoutAssignedWindows { -public: - struct ExtendWindowModeData - { - std::map windowInitialIndexSet; - std::map windowFinalIndex; - }; - public : - LayoutAssignedWindows(); + LayoutAssignedWindows() = default; ~LayoutAssignedWindows() = default; void Assign(HWND window, const ZoneIndexSet& zones); - void Extend(HWND window, const ZoneIndexSet& zones); void Dismiss(HWND window); std::map SnappedWindows() const noexcept; @@ -25,12 +17,9 @@ public : void CycleWindows(HWND window, bool reverse); - const std::unique_ptr& ExtendWindowData(); - private: std::map m_windowIndexSet{}; std::map> m_windowsByIndexSets{}; - std::unique_ptr m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition void InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet); HWND GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index 8c1789a6b9..632c6a3a78 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -80,6 +80,13 @@ public: #endif } +#if defined(UNIT_TESTS) + inline void SetSettings(const Settings& settings) + { + m_settings = settings; + } +#endif + void AddObserver(SettingsObserver& observer); void RemoveObserver(SettingsObserver& observer); diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp new file mode 100644 index 0000000000..7274c6cda9 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp @@ -0,0 +1,489 @@ +#include "pch.h" +#include "WindowKeyboardSnap.h" + +#include +#include +#include +#include +#include + +#include +#include + +bool WindowKeyboardSnap::Snap(HWND window, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas, const std::vector& 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>& activeWorkAreas, const std::vector>& 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>& 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>& activeWorkAreas, const std::vector& 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>& activeWorkAreas, const std::vector>& monitors) +{ + // Extract zones from all other monitors and target one of them + std::vector zoneRects; + std::vector> 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(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 usedZoneIndices(zones.size(), false); + auto windowZones = layoutWindows.GetZoneIndexSetFromWindow(window); + + for (const ZoneIndex id : windowZones) + { + usedZoneIndices[id] = true; + } + + std::vector 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(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(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 usedZoneIndices(zones.size(), false); + std::vector 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; +} diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h new file mode 100644 index 0000000000..086b4047ba --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +class WorkArea; + +class WindowKeyboardSnap +{ + struct ExtendWindowModeData + { + HWND window{ nullptr }; + ZoneIndexSet windowInitialIndexSet{}; + ZoneIndex windowFinalIndex{ -1 }; + + bool IsExtended(HWND wnd) const + { + return window == wnd && windowFinalIndex != -1; + } + + void Set(HWND w) + { + window = w; + windowFinalIndex = -1; + windowInitialIndexSet.clear(); + } + + void Reset() + { + window = nullptr; + windowFinalIndex = -1; + windowInitialIndexSet.clear(); + } + }; + +public: + WindowKeyboardSnap() = default; + ~WindowKeyboardSnap() = default; + + bool Snap(HWND window, HMONITOR activeMonitor, DWORD vkCode, + const std::unordered_map>& activeWorkAreas, + const std::vector& monitors); + bool Snap(HWND window, RECT windowRect, HMONITOR activeMonitor, DWORD vkCode, + const std::unordered_map>& activeWorkAreas, + const std::vector>& monitors); + bool Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas); + +private: + bool SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector& monitors); + bool SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors); + + bool MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea); + bool MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea); + bool Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea); + + ExtendWindowModeData m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition +}; diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp new file mode 100644 index 0000000000..c6b8f8cedd --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp @@ -0,0 +1,244 @@ +#include "pch.h" +#include "WindowMouseSnap.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas) : + m_window(window), + m_activeWorkAreas(activeWorkAreas), + m_currentWorkArea(nullptr), + m_snappingMode(false) +{ + m_windowProperties.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window); + m_windowProperties.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window) && + (!FancyZonesWindowUtils::IsPopupWindow(m_window) || FancyZonesSettings::settings().allowSnapPopupWindows); +} + +WindowMouseSnap::~WindowMouseSnap() +{ + ResetWindowTransparency(); +} + +std::unique_ptr WindowMouseSnap::Create(HWND window, const std::unordered_map>& activeWorkAreas) +{ + if (!FancyZonesWindowProcessing::IsProcessable(window) || + !FancyZonesWindowUtils::IsCandidateForZoning(window) || + FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) + { + return nullptr; + } + + if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window)) + { + // Notifies user if unable to drag elevated window + FancyZonesNotifications::WarnIfElevationIsRequired(); + return nullptr; + } + + return std::unique_ptr(new WindowMouseSnap(window, activeWorkAreas)); +} + +bool WindowMouseSnap::MoveSizeStart(HMONITOR monitor, bool isSnapping) +{ + auto iter = m_activeWorkAreas.find(monitor); + if (iter == end(m_activeWorkAreas)) + { + return false; + } + + m_currentWorkArea = iter->second.get(); + + SwitchSnappingMode(isSnapping); + + if (m_currentWorkArea) + { + m_currentWorkArea->Unsnap(m_window); + } + + return true; +} + +void WindowMouseSnap::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState) +{ + auto iter = m_activeWorkAreas.find(monitor); + if (isSnapping && iter != m_activeWorkAreas.end()) + { + // The drag has moved to a different monitor. + // Change work area + if (iter->second.get() != m_currentWorkArea) + { + m_highlightedZones.Reset(); + + if (m_currentWorkArea) + { + if (!FancyZonesSettings::settings().showZonesOnAllMonitors) + { + m_currentWorkArea->HideZones(); + } + else + { + m_currentWorkArea->ShowZones({}, m_window); + } + } + + m_currentWorkArea = iter->second.get(); + } + + if (m_currentWorkArea) + { + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_currentWorkArea->GetWorkAreaWindow(), &ptClient, 1); + const bool redraw = m_highlightedZones.Update(m_currentWorkArea->GetLayout().get(), ptClient, isSelectManyZonesState); + if (redraw) + { + m_currentWorkArea->ShowZones(m_highlightedZones.Zones(), m_window); + } + } + } + + SwitchSnappingMode(isSnapping); +} + +void WindowMouseSnap::MoveSizeEnd() +{ + if (m_snappingMode) + { + const bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window); + const bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window); + + if ((isStandardWindow == false && hasNoVisibleOwner == true && + m_windowProperties.isStandardWindow == true && m_windowProperties.hasNoVisibleOwner == true) || + FancyZonesWindowUtils::IsWindowMaximized(m_window)) + { + // Abort the zoning, this is a Chromium based tab that is merged back with an existing window + // or if the window is maximized by Windows when the cursor hits the screen top border + } + else if (m_currentWorkArea) + { + m_currentWorkArea->Snap(m_window, m_highlightedZones.Zones()); + } + } + else + { + FancyZonesWindowUtils::ResetRoundCornersPreference(m_window); + if (FancyZonesSettings::settings().restoreSize) + { + if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) + { + ::RemoveProp(m_window, ZonedWindowProperties::PropertyRestoreSizeID); + } + else if (!FancyZonesWindowUtils::IsWindowMaximized(m_window)) + { + FancyZonesWindowUtils::RestoreWindowSize(m_window); + } + } + } + + SwitchSnappingMode(false); +} + +void WindowMouseSnap::SwitchSnappingMode(bool isSnapping) +{ + if (!m_snappingMode && isSnapping) // turn on + { + m_highlightedZones.Reset(); + SetWindowTransparency(); + + if (FancyZonesSettings::settings().showZonesOnAllMonitors) + { + for (const auto& [_, workArea] : m_activeWorkAreas) + { + if (workArea) + { + workArea->ShowZones({}, m_window); + } + } + } + else if (m_currentWorkArea) + { + m_currentWorkArea->ShowZones({}, m_window); + } + + if (m_currentWorkArea) + { + m_currentWorkArea->Unsnap(m_window); + Trace::WorkArea::MoveOrResizeStarted(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows()); + } + } + else if (m_snappingMode && !isSnapping) // turn off + { + ResetWindowTransparency(); + m_highlightedZones.Reset(); + + // Hide all layouts (regardless of settings) + for (auto& [_, workArea] : m_activeWorkAreas) + { + if (workArea) + { + workArea->HideZones(); + } + } + + if (m_currentWorkArea) + { + Trace::WorkArea::MoveOrResizeEnd(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows()); + } + } + + m_snappingMode = isSnapping; +} + +void WindowMouseSnap::SetWindowTransparency() +{ + if (FancyZonesSettings::settings().makeDraggedWindowTransparent) + { + m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE); + + SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED); + + if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags)) + { + Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + return; + } + + if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA)) + { + Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + return; + } + + m_windowProperties.transparencySet = true; + } +} + +void WindowMouseSnap::ResetWindowTransparency() +{ + if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet) + { + bool reset = true; + if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags)) + { + Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + reset = false; + } + + if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0) + { + Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError())); + reset = false; + } + + m_windowProperties.transparencySet = !reset; + } +} diff --git a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h similarity index 82% rename from src/modules/fancyzones/FancyZonesLib/WindowDrag.h rename to src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h index 0956af9841..5891771e14 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h @@ -4,13 +4,13 @@ class WorkArea; -class WindowDrag +class WindowMouseSnap { - WindowDrag(HWND window, const std::unordered_map>& activeWorkAreas); + WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas); public: - static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas); - ~WindowDrag(); + static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas); + ~WindowMouseSnap(); bool MoveSizeStart(HMONITOR monitor, bool isSnapping); void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState); diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp index 40f22ea802..dbad77732c 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp @@ -456,15 +456,19 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect ::GetWindowRect(window, &windowRect); // Take care of borders - RECT frameRect{}; - if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) + // Skip when windowOfRect is not initialized (in unit tests) + if (windowOfRect) { - LONG leftMargin = frameRect.left - windowRect.left; - LONG rightMargin = frameRect.right - windowRect.right; - LONG bottomMargin = frameRect.bottom - windowRect.bottom; - newWindowRect.left -= leftMargin; - newWindowRect.right -= rightMargin; - newWindowRect.bottom -= bottomMargin; + RECT frameRect{}; + if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) + { + LONG leftMargin = frameRect.left - windowRect.left; + LONG rightMargin = frameRect.right - windowRect.right; + LONG bottomMargin = frameRect.bottom - windowRect.bottom; + newWindowRect.left -= leftMargin; + newWindowRect.right -= rightMargin; + newWindowRect.bottom -= bottomMargin; + } } // Take care of windows that cannot be resized @@ -475,7 +479,10 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect } // Convert to screen coordinates - MapWindowRect(windowOfRect, nullptr, &newWindowRect); + if (windowOfRect) + { + MapWindowRect(windowOfRect, nullptr, &newWindowRect); + } return newWindowRect; } diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index afed03881f..002bcdc63b 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -1,26 +1,16 @@ #include "pch.h" #include "WorkArea.h" -#include #include -#include #include "FancyZonesData/AppliedLayouts.h" #include "FancyZonesData/AppZoneHistory.h" -#include "FancyZonesDataTypes.h" -#include "SettingsObserver.h" #include "ZonesOverlay.h" -#include "trace.h" -#include "on_thread_executor.h" #include "Settings.h" #include #include #include -#include -#include -#include - // disabling warning 4458 - declaration of 'identifier' hides class member // to avoid warnings from GDI files - can't add winRT directory to external code // in the Cpp.Build.props @@ -127,284 +117,41 @@ WorkArea::~WorkArea() windowPool.FreeZonesOverlayWindow(m_window); } -void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) +bool WorkArea::Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition) { - MoveWindowIntoZoneByIndexSet(window, { index }); -} - -void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition /* = true*/) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty()) + if (!m_layout || zones.empty()) { - return; + return false; } - FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); + m_layoutWindows.Assign(window, zones); + AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, m_layout->Id(), zones); if (updatePosition) { - const auto rect = m_layout->GetCombinedZonesRect(indexSet); - if (rect.bottom - rect.top > 0 && rect.right - rect.left > 0) - { - const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); - FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); - } + const auto rect = m_layout->GetCombinedZonesRect(zones); + const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); + FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); + FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); } - SnapWindow(window, indexSet); + return FancyZonesWindowProperties::StampZoneIndexProperty(window, zones); } -bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) +bool WorkArea::Unsnap(HWND window) { - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) + if (!m_layout) { return false; } - - auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window); - const auto numZones = m_layout->Zones().size(); - - // The window was not assigned to any zone here - if (zoneIndexes.size() == 0) - { - MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0); - } - else - { - const ZoneIndex oldId = zoneIndexes[0]; - - // We reached the edge - if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast(numZones) - 1)) - { - if (!cycle) - { - return false; - } - - MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0); - } - else - { - // We didn't reach the edge - if (vkCode == VK_LEFT) - { - MoveWindowIntoZoneByIndex(window, oldId - 1); - } - else - { - MoveWindowIntoZoneByIndex(window, oldId + 1); - } - } - } + + m_layoutWindows.Dismiss(window); + AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, m_layout->Id()); + FancyZonesWindowProperties::RemoveZoneIndexProperty(window); return true; } -bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) - { - return false; - } - - const auto& zones = m_layout->Zones(); - std::vector usedZoneIndices(zones.size(), false); - auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window); - - for (const ZoneIndex id : windowZones) - { - usedZoneIndices[id] = true; - } - - std::vector zoneRects; - ZoneIndexSet freeZoneIndices; - - for (const auto& [zoneId, zone] : zones) - { - if (!usedZoneIndices[zoneId]) - { - zoneRects.emplace_back(zones.at(zoneId).GetZoneRect()); - freeZoneIndices.emplace_back(zoneId); - } - } - - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - return false; - } - - // Move to coordinates relative to windowZone - windowRect.top -= m_workAreaRect.top(); - windowRect.bottom -= m_workAreaRect.top(); - windowRect.left -= m_workAreaRect.left(); - windowRect.right -= m_workAreaRect.left(); - - auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, freeZoneIndices[result]); - Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get()); - return true; - } - 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(m_workAreaRect.left(), m_workAreaRect.top(), m_workAreaRect.right(), m_workAreaRect.bottom()), vkCode); - result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, result); - Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get()); - return true; - } - } - - return false; -} - -bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) - { - return false; - } - - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - return false; - } - - const auto& zones = m_layout->Zones(); - auto appliedZones = m_layoutWindows->GetZoneIndexSetFromWindow(window); - const auto& extendModeData = m_layoutWindows->ExtendWindowData(); - - std::vector usedZoneIndices(zones.size(), false); - std::vector 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 - auto finalIndexIt = extendModeData->windowFinalIndex.find(window); - if (finalIndexIt != extendModeData->windowFinalIndex.end()) - { - usedZoneIndices[finalIndexIt->second] = true; - windowRect = zones.at(finalIndexIt->second).GetZoneRect(); - } - else - { - for (const ZoneIndex idx : appliedZones) - { - usedZoneIndices[idx] = true; - } - // Move to coordinates relative to windowZone - windowRect.top -= m_workAreaRect.top(); - windowRect.bottom -= m_workAreaRect.top(); - windowRect.left -= m_workAreaRect.left(); - windowRect.right -= m_workAreaRect.left(); - } - - 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()) - { - ZoneIndex targetZone = freeZoneIndices[result]; - ZoneIndexSet resultIndexSet; - - // First time with selectManyZones = true for this window? - if (finalIndexIt == extendModeData->windowFinalIndex.end()) - { - // Already zoned? - if (appliedZones.size()) - { - extendModeData->windowInitialIndexSet[window] = appliedZones; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = m_layout->GetCombinedZoneRange(appliedZones, { targetZone }); - } - else - { - extendModeData->windowInitialIndexSet[window] = { targetZone }; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = { targetZone }; - } - } - else - { - auto deletethis = extendModeData->windowInitialIndexSet[window]; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = m_layout->GetCombinedZoneRange(extendModeData->windowInitialIndexSet[window], { targetZone }); - } - - const auto rect = m_layout->GetCombinedZonesRect(resultIndexSet); - const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); - FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); - - SnapWindow(window, resultIndexSet, true); - - return true; - } - - return false; -} - -void WorkArea::SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend) -{ - if (!m_layoutWindows || !m_layout) - { - return; - } - - if (extend) - { - m_layoutWindows->Extend(window, zones); - } - else - { - m_layoutWindows->Assign(window, zones); - } - - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zones); - } - - FancyZonesWindowProperties::StampZoneIndexProperty(window, zones); -} - -void WorkArea::UnsnapWindow(HWND window) -{ - if (!m_layoutWindows || !m_layout) - { - return; - } - - m_layoutWindows->Dismiss(window); - - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, guidStr.value()); - } - - FancyZonesWindowProperties::RemoveZoneIndexProperty(window); -} - const GUID WorkArea::GetLayoutId() const noexcept { if (m_layout) @@ -415,29 +162,7 @@ const GUID WorkArea::GetLayoutId() const noexcept return GUID{}; } -ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const -{ - if (m_layout) - { - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value()); - } - else - { - Logger::error(L"Failed to convert to string layout GUID on the requested work area"); - } - } - else - { - Logger::error(L"No layout initialized on the requested work area"); - } - - return {}; -} - -void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/) +void WorkArea::ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/) { if (m_layout && m_zonesOverlay) { @@ -447,7 +172,7 @@ void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindo } } -void WorkArea::HideZonesOverlay() +void WorkArea::HideZones() { if (m_zonesOverlay) { @@ -465,15 +190,10 @@ void WorkArea::FlashZones() } } -void WorkArea::UpdateActiveZoneSet() +void WorkArea::InitLayout() { - const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId); - if (!isLayoutAlreadyApplied) - { - AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); - } + InitLayout({}); - CalculateZoneSet(); if (m_window && m_layout) { m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); @@ -482,24 +202,16 @@ void WorkArea::UpdateActiveZoneSet() void WorkArea::UpdateWindowPositions() { - if (!m_layoutWindows) - { - return; - } - - const auto& snappedWindows = m_layoutWindows->SnappedWindows(); + const auto& snappedWindows = m_layoutWindows.SnappedWindows(); for (const auto& [window, zones] : snappedWindows) { - MoveWindowIntoZoneByIndexSet(window, zones, true); + Snap(window, zones, true); } } void WorkArea::CycleWindows(HWND window, bool reverse) { - if (m_layoutWindows) - { - m_layoutWindows->CycleWindows(window, reverse); - } + m_layoutWindows.CycleWindows(window, reverse); } #pragma region private @@ -537,6 +249,46 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) CalculateZoneSet(); } +void WorkArea::InitSnappedWindows() +{ + static bool updatePositionOnceOnStartFlag = true; + Logger::info(L"Init work area {} windows, update positions = {}", m_uniqueId.toString(), updatePositionOnceOnStartFlag); + + for (const auto& window : VirtualDesktop::instance().GetWindowsFromCurrentDesktop()) + { + auto indexes = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); + if (indexes.size() == 0) + { + continue; + } + + if (!m_uniqueId.monitorId.monitor) // one work area across monitors + { + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + else + { + const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor && m_uniqueId.monitorId.monitor == monitor) + { + // prioritize snapping on the current monitor if the window was snapped to several work areas + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + else + { + // if the window is not snapped on the current monitor, then check the others + auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, GetLayoutId()); + if (savedIndexes == indexes) + { + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + } + } + } + + updatePositionOnceOnStartFlag = false; +} + void WorkArea::CalculateZoneSet() { const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId); @@ -548,11 +300,6 @@ void WorkArea::CalculateZoneSet() m_layout = std::make_unique(appliedLayout.value()); m_layout->Init(m_workAreaRect, m_uniqueId.monitorId.monitor); - - if (!m_layoutWindows) - { - m_layoutWindows = std::make_unique(); - } } LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 5e04c87aee..ecd97fd849 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -39,26 +38,20 @@ public: FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; } const std::unique_ptr& GetLayout() const noexcept { return m_layout; } - const std::unique_ptr& GetLayoutWindows() const noexcept { return m_layoutWindows; } + const LayoutAssignedWindows& GetLayoutWindows() const noexcept { return m_layoutWindows; } const HWND GetWorkAreaWindow() const noexcept { return m_window; } const GUID GetLayoutId() const noexcept; + const FancyZonesUtils::Rect& GetWorkAreaRect() const noexcept { return m_workAreaRect; } - ZoneIndexSet GetWindowZoneIndexes(HWND window) const; - - void MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index); - void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition = true); - bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle); - bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle); - bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode); - - void SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend = false); - void UnsnapWindow(HWND window); - - void UpdateActiveZoneSet(); + void InitLayout(); + void InitSnappedWindows(); void UpdateWindowPositions(); - void ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr); - void HideZonesOverlay(); + bool Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition = true); + bool Unsnap(HWND window); + + void ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr); + void HideZones(); void FlashZones(); void CycleWindows(HWND window, bool reverse); @@ -79,6 +72,6 @@ private: const FancyZonesDataTypes::WorkAreaId m_uniqueId; HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. std::unique_ptr m_layout; - std::unique_ptr m_layoutWindows; + LayoutAssignedWindows m_layoutWindows{}; std::unique_ptr m_zonesOverlay; }; diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 00cbfbd064..0fce61584f 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -90,17 +90,17 @@ struct ZoneSetInfo }; -ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, const LayoutAssignedWindows& layoutWindows) noexcept { ZoneSetInfo info; - if (layout && layoutWindows) + if (layout) { auto zones = layout->Zones(); info.NumberOfZones = zones.size(); info.NumberOfWindows = 0; for (int i = 0; i < static_cast(zones.size()); i++) { - if (!layoutWindows->IsZoneEmpty(i)) + if (!layoutWindows.IsZoneEmpty(i)) { info.NumberOfWindows++; } @@ -258,7 +258,7 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } -void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept +void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -271,7 +271,7 @@ void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssign TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept +void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -356,7 +356,7 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept TraceLoggingValue(wParam, KeyboardValueKey)); } -void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -369,7 +369,7 @@ void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -382,7 +382,7 @@ void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ La TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept +void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( diff --git a/src/modules/fancyzones/FancyZonesLib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h index 9555f5563d..3bef5cc8c1 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.h +++ b/src/modules/fancyzones/FancyZonesLib/trace.h @@ -19,8 +19,8 @@ public: static void EditorLaunched(int value) noexcept; static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; static void QuickLayoutSwitched(bool shortcutUsed) noexcept; - static void SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept; - static void KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept; + static void SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; }; static void SettingsTelemetry(const Settings& settings) noexcept; @@ -36,8 +36,8 @@ public: }; static void KeyUp(WPARAM wparam) noexcept; - static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept; - static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept; - static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept; + static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept; }; }; diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp index 4b4ccc40cb..6bc1a2aee2 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.cpp +++ b/src/modules/fancyzones/FancyZonesLib/util.cpp @@ -123,6 +123,15 @@ namespace FancyZonesUtils monitorInfo = std::move(sortedMonitorInfo); } + std::vector GetMonitorsOrdered() + { + auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); + FancyZonesUtils::OrderMonitors(monitors); + std::vector output; + std::transform(std::begin(monitors), std::end(monitors), std::back_inserter(output), [](const auto& info) { return info.first; }); + return output; + } + bool IsValidGuid(const std::wstring& str) { GUID id; diff --git a/src/modules/fancyzones/FancyZonesLib/util.h b/src/modules/fancyzones/FancyZonesLib/util.h index 41d1a9b4d8..98105a1af0 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.h +++ b/src/modules/fancyzones/FancyZonesLib/util.h @@ -143,13 +143,12 @@ namespace FancyZonesUtils } template - RECT GetAllMonitorsCombinedRect() + RECT GetMonitorsCombinedRect(const std::vector>& monitorRects) { - auto allMonitors = GetAllMonitorRects(); bool empty = true; RECT result{ 0, 0, 0, 0 }; - for (auto& [monitor, rect] : allMonitors) + for (auto& [monitor, rect] : monitorRects) { if (empty) { @@ -168,6 +167,13 @@ namespace FancyZonesUtils return result; } + template + RECT GetAllMonitorsCombinedRect() + { + auto allMonitors = GetAllMonitorRects(); + return GetMonitorsCombinedRect(allMonitors); + } + constexpr RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept { LONG deltaX = 0, deltaY = 0; @@ -196,6 +202,7 @@ namespace FancyZonesUtils UINT GetDpiForMonitor(HMONITOR monitor) noexcept; void OrderMonitors(std::vector>& monitorInfo); + std::vector GetMonitorsOrdered(); bool IsValidGuid(const std::wstring& str); std::optional GuidFromString(const std::wstring& str) noexcept; diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp index f1365f2079..c3e954fb00 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp @@ -207,22 +207,22 @@ namespace FancyZonesUnitTests TEST_METHOD (AppLastZoneInvalidWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::Window(); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); const int expectedZoneIndex = 1; - Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex })); + Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex })); } TEST_METHOD (AppLastZoneNullWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -230,12 +230,12 @@ namespace FancyZonesUnitTests const auto window = nullptr; const int expectedZoneIndex = 1; - Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex })); + Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex })); } TEST_METHOD (AppLastdeviceIdTest) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId1{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -247,15 +247,15 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const int expectedZoneIndex = 10; - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, zoneSetId, { expectedZoneIndex })); - Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, layoutId, { expectedZoneIndex })); + Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, layoutId)); } TEST_METHOD (AppLastZoneSetIdTest) { - const std::wstring zoneSetId1 = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}"; - const std::wstring zoneSetId2 = L"{B7A1F5A9-9DC2-4505-84AB-993253839094}"; + const auto layoutId1 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value(); + const auto layoutId2 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839094}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -263,56 +263,56 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const int expectedZoneIndex = 10; - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId1, { expectedZoneIndex })); - Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId1)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId2)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId1, { expectedZoneIndex })); + Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId1)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId2)); } TEST_METHOD (AppLastZoneRemoveWindow) { - const std::wstring zoneSetId = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { 1 })); - Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { 1 })); + Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); } TEST_METHOD (AppLastZoneRemoveUnknownWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); } TEST_METHOD (AppLastZoneRemoveUnknownZoneSetId) { - const std::wstring zoneSetIdToInsert = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; - const std::wstring zoneSetIdToRemove = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}"; + const auto layoutIdToInsert = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); + const auto layoutIdToRemove = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetIdToInsert, { 1 })); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetIdToRemove)); - Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetIdToInsert)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutIdToInsert, { 1 })); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutIdToRemove)); + Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutIdToInsert)); } TEST_METHOD (AppLastZoneRemoveUnknownWindowId) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaIdToInsert{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -323,20 +323,20 @@ namespace FancyZonesUnitTests }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, zoneSetId, { 1 })); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, zoneSetId)); - Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, layoutId, { 1 })); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, layoutId)); + Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, layoutId)); } TEST_METHOD (AppLastZoneRemoveNullWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, zoneSetId)); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId)); } }; } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp index fe3c262415..dff898bd7b 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp @@ -140,10 +140,9 @@ namespace FancyZonesUnitTests data.zoneCount = 4; // prepare settings - PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey); - values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest))); - json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json()); - FancyZonesSettings::instance().LoadSettings(); + auto settings = FancyZonesSettings::settings(); + settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; + FancyZonesSettings::instance().SetSettings(settings); auto layout = std::make_unique(data); layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); @@ -168,10 +167,9 @@ namespace FancyZonesUnitTests data.zoneCount = 4; // prepare settings - PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey); - values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest))); - json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json()); - FancyZonesSettings::instance().LoadSettings(); + auto settings = FancyZonesSettings::settings(); + settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; + FancyZonesSettings::instance().SetSettings(settings); auto layout = std::make_unique(data); layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index f00a1e8c5d..baa9cfef4b 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -55,6 +55,7 @@ + diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters index 799ea74a4a..c8679dd8dc 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters @@ -63,6 +63,9 @@ Source Files + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp new file mode 100644 index 0000000000..2b18523ea6 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp @@ -0,0 +1,1452 @@ +#include "pch.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (WindowKeyboardSnap_ByIndex_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_INITIALIZE (Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + FancyZonesSettings::instance().SetSettings({ .overrideSnapHotkeys = true, .moveWindowsBasedOnPosition = false }); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveToNextZone) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveToPrevZone) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveNext_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MovePrev_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveNext_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MovePrev_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + }; + + TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByIndex_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::vector m_monitors = { Mocks::Monitor(), Mocks::Monitor() }; + + const std::vector m_workAreaIds = { + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[0], + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[1], + .deviceId = { + .id = L"device-id-2", + .instanceId = L"5&10a58c63&0&UID16777489", + .number = 2, + }, + .serialNumber = L"serial-number-2" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() } + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + for (const auto& workAreaId : m_workAreaIds) + { + layoutsArray.Append(WorkAreaLayoutObject(workAreaId)); + } + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitors[0], std::move(workArea1) }); + + auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ RECT(1920, 0, 3840, 1080) }); + m_workAreaMap.insert({ m_monitors[1], std::move(workArea2) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[1]]->Snap(window, { 0 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[0])->Snap(window, { 3 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[0]]->Snap(window, { 0 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[0]]->Snap(window, { 0 }); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 }); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + }; + + TEST_CLASS (WindowKeyboardSnap_ByPosition_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + const RECT m_rect = RECT{ 0, 0, 1920, 1080 }; + + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + // using zone rects instead of the actual window rect + // otherwise we'll need to wait after snapping, for the window to be resized + RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ m_rect }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Move_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveLeft_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveRight_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveUp_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveDown_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + }; + + TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByPosition_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::vector> m_monitors = { + { Mocks::Monitor(), RECT{ 0, 0, 1920, 1080 } }, // left + { Mocks::Monitor(), RECT{ 1920, 0, 3840, 1080 } }, // right + { Mocks::Monitor(), RECT{ 0, -1080, 1920, 0 } } // top + }; + + const std::vector m_workAreaIds = { + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[0].first, + .deviceId = { + .id = L"device-id-left", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-left" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[1].first, + .deviceId = { + .id = L"device-id-right", + .instanceId = L"5&10a58c63&0&UID16777489", + .number = 2, + }, + .serialNumber = L"serial-number-right" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[2].first, + .deviceId = { + .id = L"device-id-top", + .instanceId = L"5&10a58c63&0&UID16777487", + .number = 3, + }, + .serialNumber = L"serial-number-top" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() } + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + for (const auto& workAreaId : m_workAreaIds) + { + layoutsArray.Append(WorkAreaLayoutObject(workAreaId)); + } + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + rect.left += workAreaRect.left(); + rect.right += workAreaRect.left(); + rect.top += workAreaRect.top(); + rect.bottom += workAreaRect.top(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ m_monitors[0].second }); + m_workAreaMap.insert({ m_monitors[0].first, std::move(workArea1) }); + + auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ m_monitors[1].second }); + m_workAreaMap.insert({ m_monitors[1].first, std::move(workArea2) }); + + auto workArea3 = WorkArea::Create(m_hInst, m_workAreaIds[2], {}, FancyZonesUtils::Rect{ m_monitors[2].second }); + m_workAreaMap.insert({ m_monitors[2].first, std::move(workArea3) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Left_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 3; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Up_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Down_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 2; + m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 3; + m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Up_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Down_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 2; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + }; + + TEST_CLASS (WindowKeyboardSnap_Extend_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + std::unordered_map> m_workAreaMap = {}; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + rect.left += workAreaRect.left(); + rect.right += workAreaRect.left(); + rect.top += workAreaRect.top(); + rect.bottom += workAreaRect.top(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD(ExtendNonSnappedWindow) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect = {10,10,150,150}; + Assert::IsTrue(SetWindowPos(window, nullptr, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, 0)); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = m_workAreaMap[m_monitor]->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaMap[m_monitor]->UniqueId(), m_workAreaMap[m_monitor]->GetLayoutId()).size()); + } + + TEST_METHOD (ExtendSnappedWindow) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendLeft) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 1 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 1); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendRight) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendUp) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 2 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 2); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendDown) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendAndRevert) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + }; + + TEST_CLASS(WindowKeyboardSnap_MultiMonitorMode_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + HINSTANCE m_hInst{}; + const RECT m_rect = RECT{ 0, 0, 1920, 1080 }; + const HMONITOR m_monitor = nullptr; + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"FancyZones", + .instanceId = L"MultiMonitorDevice", + .number = 0, + }, + .serialNumber = L"" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + std::unordered_map> m_workAreaMap = {}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + // use zone rects instead of the actual window rect + // otherwise we'll need to wait after snapping, for the window to be resized + // important when snapping/extending by position + RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD(Snap_ByIndex) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD(Move_ByIndex) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_ByIndex_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Snap_ByPosition) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + } + + TEST_METHOD (Move_ByPosition) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_ByPosition_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Extend) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp index 99d5119cb6..c365a06c03 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp @@ -44,7 +44,6 @@ namespace FancyZonesUnitTests { std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); - std::filesystem::remove(DefaultLayouts::DefaultLayoutsFileName()); } @@ -58,7 +57,6 @@ namespace FancyZonesUnitTests const auto& layout = workArea->GetLayout(); Assert::IsNotNull(layout.get()); - Assert::IsNotNull(workArea->GetLayoutWindows().get()); Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type())); Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size())); } @@ -73,7 +71,6 @@ namespace FancyZonesUnitTests const auto& layout = workArea->GetLayout(); Assert::IsNotNull(layout.get()); - Assert::IsNotNull(workArea->GetLayoutWindows().get()); Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type())); Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size())); } @@ -102,7 +99,6 @@ namespace FancyZonesUnitTests auto actualWorkArea = WorkArea::Create(m_hInst, m_workAreaId, parentUniqueId, m_workAreaRect); Assert::IsNotNull(actualWorkArea->GetLayout().get()); - Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get()); Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_workAreaId)); const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_workAreaId); @@ -179,428 +175,46 @@ namespace FancyZonesUnitTests } }; - TEST_CLASS (WorkAreaMoveWindowUnitTests) + TEST_CLASS (WorkAreaSnapUnitTests) { - const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; - const FancyZonesDataTypes::WorkAreaId m_workAreaId{ + HINSTANCE m_hInst{}; + const HMONITOR m_monitor = Mocks::Monitor(); + const FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) }; + const FancyZonesDataTypes::WorkAreaId m_parentUniqueId = {}; + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { .monitorId = { - .monitor = Mocks::Monitor(), - .deviceId = { - .id = L"DELA026", + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", .instanceId = L"5&10a58c63&0&UID16777488", .number = 1, }, - .serialNumber = L"serial-number" - }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{310F2924-B587-4D87-97C2-90031BDBE3F1}").value() }; - FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty - - HINSTANCE m_hInst{}; - FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) }; - - void PrepareEmptyLayout() - { - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - - { - json::JsonObject layout{}; - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Blank))); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(0)); - - json::JsonObject workAreaId{}; - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); - - json::JsonObject obj{}; - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId); - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); - - layoutsArray.Append(obj); - } - - root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); - json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); - - AppliedLayouts::instance().LoadData(); - } - - void PrepareGridLayout() - { - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - - { - json::JsonObject layout{}; - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); - - json::JsonObject workAreaId{}; - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); - - json::JsonObject obj{}; - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId); - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); - - layoutsArray.Append(obj); - } - - root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); - json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); - - AppliedLayouts::instance().LoadData(); - } - TEST_METHOD_INITIALIZE(Init) noexcept { AppZoneHistory::instance().LoadData(); - AppliedLayouts::instance().LoadData(); } TEST_METHOD_CLEANUP(CleanUp) noexcept { std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); - std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); - } - - TEST_METHOD (EmptyZonesMoveLeftByIndex) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (EmptyZonesRightByIndex) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNonAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightNonAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - const auto& layoutWindows = workArea->GetLayoutWindows(); - - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - } - - TEST_METHOD (MoveAppliedWindowByIndexCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ static_cast(workArea->GetLayout()->Zones().size() - 1) } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByIndexNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (EmptyZonesMoveByPosition) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNonAppliedWindowByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightNonAppliedWindowByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowHorizontallyByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowVerticallyByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_DOWN, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionHorizontallyCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionHorizontallyNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionVerticallyCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - const auto& layoutWindows = workArea->GetLayoutWindows(); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionVerticallyNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (ExtendZoneHorizontally) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->ExtendWindowByDirectionAndPosition(window, VK_RIGHT); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (ExtendZoneVertically) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->ExtendWindowByDirectionAndPosition(window, VK_DOWN); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); } TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) { + LayoutData layout{ + .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(), + .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 0, + .zoneCount = 4, + .sensitivityRadius = 20, + }; + AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout); + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); @@ -610,7 +224,10 @@ namespace FancyZonesUnitTests SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW); SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); + Assert::IsTrue(workArea->Snap(window, { 1 }, true)); + + // wait for the window to be resized + std::this_thread::sleep_for(std::chrono::milliseconds(10)); RECT inZoneRect; GetWindowRect(window, &inZoneRect); @@ -619,13 +236,46 @@ namespace FancyZonesUnitTests Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top); } + TEST_METHOD (WhenWindowIsResizablePlacingItIntoTheZoneShouldResizeIt) + { + LayoutData layout{ + .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(), + .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 0, + .zoneCount = 4, + .sensitivityRadius = 20, + }; + AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout); + + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + SetWindowPos(window, nullptr, 150, 150, 450, 550, SWP_SHOWWINDOW); + + Assert::IsTrue(workArea->Snap(window, { 1 }, true)); + + // wait for the window to be resized + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + RECT zonedWindowRect; + GetWindowRect(window, &zonedWindowRect); + + RECT zoneRect = workArea->GetLayout()->Zones().at(1).GetZoneRect(); + + Assert::AreEqual(zoneRect.left, zonedWindowRect.left); + Assert::AreEqual(zoneRect.right, zonedWindowRect.right); + Assert::AreEqual(zoneRect.top, zonedWindowRect.top); + Assert::AreEqual(zoneRect.bottom, zonedWindowRect.bottom); + } + TEST_METHOD (SnapWindowPropertyTest) { const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); const ZoneIndexSet expected = { 1, 2 }; - workArea->SnapWindow(window, expected); + Assert::IsTrue(workArea->Snap(window, expected)); const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); Assert::AreEqual(expected.size(), actual.size()); @@ -641,7 +291,7 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const ZoneIndexSet expected = { 1, 2 }; - workArea->SnapWindow(window, expected); + Assert::IsTrue(workArea->Snap(window, expected)); const auto processPath = get_process_path(window); const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId); @@ -654,13 +304,25 @@ namespace FancyZonesUnitTests } } + TEST_METHOD (SnapLayoutAssignedWindowsTest) + { + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + const ZoneIndexSet expected = { 1, 2 }; + Assert::IsTrue(workArea->Snap(window, expected)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(expected == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + TEST_METHOD (UnsnapPropertyTest) { const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); - workArea->SnapWindow(window, { 1, 2 }); - workArea->UnsnapWindow(window); + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); Assert::IsTrue(actual.empty()); @@ -671,13 +333,25 @@ namespace FancyZonesUnitTests const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); - workArea->SnapWindow(window, { 1, 2 }); - workArea->UnsnapWindow(window); + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); const auto processPath = get_process_path(window); const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId); Assert::IsFalse(history.has_value()); } + + TEST_METHOD (UnsnapLayoutAssignedWindowsTest) + { + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(layoutWindows.GetZoneIndexSetFromWindow(window).empty()); + } }; }