diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index a0a8891021..16d677d80c 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -102,9 +101,9 @@ public: m_windowMoveHandler.MoveSizeUpdate(monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId())); } - void MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept + void MoveSizeEnd(HWND window) noexcept { - m_windowMoveHandler.MoveSizeEnd(window, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId())); + m_windowMoveHandler.MoveSizeEnd(window, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId())); } IFACEMETHODIMP_(void) @@ -155,7 +154,7 @@ protected: private: void UpdateWorkAreas() noexcept; - void CycleTabs(bool reverse) 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; @@ -342,7 +341,7 @@ void FancyZones::MoveWindowIntoZone(HWND window, std::shared_ptr workA { if (workArea) { - Trace::FancyZones::SnapNewWindowIntoZone(workArea->ZoneSet()); + Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); } m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea); AppZoneHistory::instance().UpdateProcessIdToHandleMap(window, workArea->UniqueId()); @@ -434,7 +433,14 @@ void FancyZones::WindowCreated(HWND window) noexcept // Open on active monitor if window wasn't zoned if (openOnActiveMonitor && !movedToAppLastZone) { - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { MonitorUtils::OpenWindowOnActiveMonitor(window, active); } }).wait(); + // window is recreated after switching virtual desktop + // avoid moving already opened windows after switching vd + bool isMoved = FancyZonesWindowProperties::RetreiveMovedOnOpeningProperty(window); + if (!isMoved) + { + FancyZonesWindowProperties::StampMovedOnOpeningProperty(window); + m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { MonitorUtils::OpenWindowOnActiveMonitor(window, active); } }).wait(); + } } } @@ -561,7 +567,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa else if (wparam == static_cast(HotkeyId::NextTab) || wparam == static_cast(HotkeyId::PrevTab)) { bool reverse = wparam == static_cast(HotkeyId::PrevTab); - CycleTabs(reverse); + CycleWindows(reverse); } } break; @@ -630,7 +636,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa else if (message == WM_PRIV_MOVESIZEEND) { auto hwnd = reinterpret_cast(wparam); - MoveSizeEnd(hwnd, ptScreen); + MoveSizeEnd(hwnd); } else if (message == WM_PRIV_LOCATIONCHANGE) { @@ -708,13 +714,10 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept UpdateWorkAreas(); - if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) + if (FancyZonesSettings::settings().displayChange_moveWindows) { - if (FancyZonesSettings::settings().displayChange_moveWindows) - { - auto activeWorkAreas = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()); - m_windowMoveHandler.UpdateWindowsPositions(activeWorkAreas); - } + auto activeWorkAreas = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()); + m_windowMoveHandler.UpdateWindowsPositions(activeWorkAreas); } } @@ -782,7 +785,7 @@ void FancyZones::UpdateWorkAreas() noexcept } } -void FancyZones::CycleTabs(bool reverse) noexcept +void FancyZones::CycleWindows(bool reverse) noexcept { auto window = GetForegroundWindow(); HMONITOR current = WorkAreaKeyFromWindow(window); @@ -790,7 +793,7 @@ void FancyZones::CycleTabs(bool reverse) noexcept auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current); if (workArea) { - workArea->CycleTabs(window, reverse); + workArea->CycleWindows(window, reverse); } } @@ -808,7 +811,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), *currMonitorInfo); if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea)) { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + 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). @@ -844,7 +847,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce } else { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); } return moved; } @@ -853,7 +856,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, workArea); if (moved) { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); } return moved; } @@ -894,13 +897,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor); if (workArea) { - auto zoneSet = workArea->ZoneSet(); - if (zoneSet) + const auto& layout = workArea->GetLayout(); + if (layout) { - const auto zones = zoneSet->GetZones(); + const auto& zones = layout->Zones(); for (const auto& [zoneId, zone] : zones) { - RECT zoneRect = zone->GetZoneRect(); + RECT zoneRect = zone.GetZoneRect(); zoneRect.left += monitorRect.left; zoneRect.right += monitorRect.left; @@ -929,7 +932,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept // Moving to another monitor succeeded const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); return true; } @@ -942,13 +945,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current); if (workArea) { - auto zoneSet = workArea->ZoneSet(); - if (zoneSet) + const auto& layout = workArea->GetLayout(); + if (layout) { - const auto zones = zoneSet->GetZones(); + const auto& zones = layout->Zones(); for (const auto& [zoneId, zone] : zones) { - RECT zoneRect = zone->GetZoneRect(); + RECT zoneRect = zone.GetZoneRect(); zoneRect.left += currentMonitorRect.left; zoneRect.right += currentMonitorRect.left; @@ -974,7 +977,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept // Moving to another monitor succeeded const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); return true; } else @@ -1013,7 +1016,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea); if (result) { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); } return result; } @@ -1022,7 +1025,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea); if (result) { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); } return result; } @@ -1130,7 +1133,20 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept HMONITOR monitor = WorkAreaKeyFromWindow(window); auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor); - if (workArea && workArea->ZoneSet() && workArea->ZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) + if (!workArea) + { + Logger::error(L"No work area for processing snap hotkey"); + return false; + } + + const auto& layout = workArea->GetLayout(); + if (!layout) + { + Logger::error(L"No layout for processing snap hotkey"); + return false; + } + + if (layout->Type() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) { if (vkCode == VK_UP || vkCode == VK_DOWN) { @@ -1142,6 +1158,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept } } } + return false; } @@ -1193,7 +1210,7 @@ std::vector> FancyZones::GetRawMonitorData() noexcept const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()); for (const auto& [monitor, workArea] : activeWorkAreaMap) { - if (workArea->ZoneSet() != nullptr) + if (workArea->GetLayout() != nullptr) { MONITORINFOEX mi; mi.cbSize = sizeof(mi); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp index 386f9d080a..0ca7a4a21b 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp @@ -556,7 +556,7 @@ void AppZoneHistory::UpdateProcessIdToHandleMap(HWND window, const FancyZonesDat } } -ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) const +ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const { auto processPath = get_process_path_waiting_uwp(window); if (processPath.empty()) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h index 569bc8cf48..45144a7a06 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h @@ -54,7 +54,7 @@ public: bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; void UpdateProcessIdToHandleMap(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId); - ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) const; + ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const; void SyncVirtualDesktops(); void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp index a4f92279bd..56c930a02f 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp @@ -17,7 +17,7 @@ namespace { // didn't use default constants since if they'll be changed later, it'll break this function - bool isLayoutDefault(const Layout& layout) + bool isLayoutDefault(const LayoutData& layout) { return layout.type == FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid && layout.zoneCount == 3 && @@ -31,11 +31,11 @@ namespace JsonUtils { struct LayoutJSON { - static std::optional FromJson(const json::JsonObject& json) + static std::optional FromJson(const json::JsonObject& json) { try { - Layout data{}; + LayoutData data{}; auto idStr = json.GetNamedString(NonLocalizable::AppliedLayoutsIds::UuidID); auto id = FancyZonesUtils::GuidFromString(idStr.c_str()); if (!id.has_value()) @@ -58,7 +58,7 @@ namespace JsonUtils } } - static json::JsonObject ToJson(const Layout& data) + static json::JsonObject ToJson(const LayoutData& data) { json::JsonObject result{}; result.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value())); @@ -141,7 +141,7 @@ namespace JsonUtils public: FancyZonesDataTypes::WorkAreaId workAreaId; - Layout data{}; + LayoutData data{}; bool hasResolutionInId = false; static std::optional FromJson(const json::JsonObject& json) @@ -427,7 +427,7 @@ void AppliedLayouts::RemoveDeletedVirtualDesktops(const std::vector& activ } } -std::optional AppliedLayouts::GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept +std::optional AppliedLayouts::GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept { auto iter = m_layouts.find(id); if (iter != m_layouts.end()) @@ -449,7 +449,7 @@ bool AppliedLayouts::IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id) return iter != m_layouts.end(); } -bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, Layout layout) +bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, LayoutData layout) { m_layouts[deviceId] = std::move(layout); return true; @@ -467,8 +467,20 @@ bool AppliedLayouts::ApplyDefaultLayout(const FancyZonesDataTypes::WorkAreaId& d return false; } - // TODO: vertical or horizontal - m_layouts[deviceId] = DefaultLayouts::instance().GetDefaultLayout(); + MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal; + MONITORINFOEX monitorInfo; + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfo(deviceId.monitorId.monitor, &monitorInfo)) + { + LONG width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; + LONG height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; + if (height > width) + { + type = MonitorConfiguraionType::Vertical; + } + } + + m_layouts[deviceId] = DefaultLayouts::instance().GetDefaultLayout(type); // Saving default layout data doesn't make sense, since it's ignored on parsing. // Given that default layouts are ignored when parsing, diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h index bd19a207e1..56a0f02191 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -35,7 +35,7 @@ namespace NonLocalizable class AppliedLayouts { public: - using TAppliedLayoutsMap = std::unordered_map; + using TAppliedLayoutsMap = std::unordered_map; static AppliedLayouts& instance(); @@ -55,12 +55,12 @@ public: void SyncVirtualDesktops(); void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); - std::optional GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; + std::optional GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept; bool IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; - bool ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, Layout layout); + bool ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, LayoutData layout); bool ApplyDefaultLayout(const FancyZonesDataTypes::WorkAreaId& deviceId); bool CloneLayout(const FancyZonesDataTypes::WorkAreaId& srcId, const FancyZonesDataTypes::WorkAreaId& dstId); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.cpp index ccab8a2f79..4900171332 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.cpp @@ -216,7 +216,7 @@ void CustomLayouts::LoadData() } } -std::optional CustomLayouts::GetLayout(const GUID& id) const noexcept +std::optional CustomLayouts::GetLayout(const GUID& id) const noexcept { auto iter = m_layouts.find(id); if (iter == m_layouts.end()) @@ -225,7 +225,7 @@ std::optional CustomLayouts::GetLayout(const GUID& id) const noexcept } FancyZonesDataTypes::CustomLayoutData customLayout = iter->second; - Layout layout{ + LayoutData layout{ .uuid = id, .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom }; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.h index 4b0aea5a70..8c960c8a9c 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/CustomLayouts.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include @@ -64,7 +64,7 @@ public: void LoadData(); - std::optional GetLayout(const GUID& id) const noexcept; + std::optional GetLayout(const GUID& id) const noexcept; std::optional GetCustomLayoutData(const GUID& id) const noexcept; const TCustomLayoutMap& GetAllLayouts() const noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.cpp index 0cd9a38494..541ce2a6c2 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.cpp @@ -34,11 +34,11 @@ namespace DefaultLayoutsJsonUtils struct LayoutJSON { - static std::optional FromJson(const json::JsonObject& json) + static std::optional FromJson(const json::JsonObject& json) { try { - Layout data{}; + LayoutData data{}; auto idStr = json.GetNamedString(NonLocalizable::DefaultLayoutsIds::UuidID, L""); if (!idStr.empty()) { @@ -65,7 +65,7 @@ namespace DefaultLayoutsJsonUtils } } - static json::JsonObject ToJson(const Layout& data) + static json::JsonObject ToJson(const LayoutData& data) { json::JsonObject result{}; result.SetNamedValue(NonLocalizable::DefaultLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value())); @@ -81,7 +81,7 @@ namespace DefaultLayoutsJsonUtils struct DefaultLayoutJSON { MonitorConfiguraionType monitorConfigurationType{ MonitorConfiguraionType::Horizontal }; - Layout layout{}; + LayoutData layout{}; static std::optional FromJson(const json::JsonObject& json) { @@ -163,7 +163,7 @@ void DefaultLayouts::LoadData() } } -Layout DefaultLayouts::GetDefaultLayout(MonitorConfiguraionType type) const noexcept +LayoutData DefaultLayouts::GetDefaultLayout(MonitorConfiguraionType type) const noexcept { auto iter = m_layouts.find(type); if (iter != m_layouts.end()) @@ -171,5 +171,5 @@ Layout DefaultLayouts::GetDefaultLayout(MonitorConfiguraionType type) const noex return iter->second; } - return Layout{}; + return LayoutData{}; } diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.h index ac5876426a..e797d2871c 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/DefaultLayouts.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -31,7 +31,7 @@ enum class MonitorConfiguraionType class DefaultLayouts { public: - using TDefaultLayoutsContainer = std::map; + using TDefaultLayoutsContainer = std::map; static DefaultLayouts& instance(); @@ -46,7 +46,7 @@ public: void LoadData(); - Layout GetDefaultLayout(MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal) const noexcept; + LayoutData GetDefaultLayout(MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal) const noexcept; private: DefaultLayouts(); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/Layout.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutData.h similarity index 89% rename from src/modules/fancyzones/FancyZonesLib/FancyZonesData/Layout.h rename to src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutData.h index f49460b456..454e279ea4 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/Layout.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutData.h @@ -5,7 +5,7 @@ #include #include -struct Layout +struct LayoutData { GUID uuid = GUID_NULL; FancyZonesDataTypes::ZoneSetLayoutType type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid; @@ -15,7 +15,7 @@ struct Layout int sensitivityRadius = DefaultValues::SensitivityRadius; }; -inline bool operator==(const Layout& lhs, const Layout& rhs) +inline bool operator==(const LayoutData& lhs, const LayoutData& rhs) { return lhs.uuid == rhs.uuid && lhs.type == rhs.type && diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.cpp index 3341cdd196..55ff0b8a60 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.cpp @@ -10,11 +10,11 @@ namespace JsonUtils { struct TemplateLayoutJSON { - static std::optional FromJson(const json::JsonObject& json) + static std::optional FromJson(const json::JsonObject& json) { try { - Layout data; + LayoutData data; data.uuid = GUID_NULL; data.type = FancyZonesDataTypes::TypeFromString(std::wstring{ json.GetNamedString(NonLocalizable::LayoutTemplatesIds::TypeID) }); @@ -32,9 +32,9 @@ namespace JsonUtils } }; - std::vector ParseJson(const json::JsonObject& json) + std::vector ParseJson(const json::JsonObject& json) { - std::vector vec{}; + std::vector vec{}; auto layouts = json.GetNamedArray(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID); for (uint32_t i = 0; i < layouts.Size(); ++i) @@ -85,7 +85,7 @@ void LayoutTemplates::LoadData() } } -std::optional LayoutTemplates::GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept +std::optional LayoutTemplates::GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept { for (const auto& layout : m_layouts) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h index f783057b76..1e8a5bc126 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LayoutTemplates.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -35,12 +35,12 @@ public: void LoadData(); - std::optional GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept; + std::optional GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept; private: LayoutTemplates(); ~LayoutTemplates() = default; std::unique_ptr m_fileWatcher; - std::vector m_layouts; + std::vector m_layouts; }; \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index b5f1cc81f8..253b592a63 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -42,7 +42,7 @@ - + @@ -53,7 +53,9 @@ + + @@ -73,7 +75,6 @@ - @@ -104,7 +105,9 @@ ../pch.h + + @@ -119,7 +122,6 @@ - diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index cf92ce9d38..e37de33469 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -30,9 +30,6 @@ Header Files - - Header Files - Header Files @@ -99,6 +96,12 @@ Header Files + + Header Files + + + Header Files + Header Files @@ -129,9 +132,6 @@ Header Files\FancyZonesData - - Header Files\FancyZonesData - Header Files\FancyZonesData @@ -144,6 +144,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files\FancyZonesData + @@ -152,9 +161,6 @@ Source Files - - Source Files - Source Files @@ -236,6 +242,12 @@ Source Files\FancyZonesData + + Source Files + + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp index 5a46397975..eda80ba329 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp @@ -83,6 +83,17 @@ ZoneIndexSet FancyZonesWindowProperties::RetrieveZoneIndexProperty(HWND window) return bitmask.ToIndexSet(); } +void FancyZonesWindowProperties::StampMovedOnOpeningProperty(HWND window) +{ + ::SetPropW(window, ZonedWindowProperties::PropertyMovedOnOpening, (HANDLE)1); +} + +bool FancyZonesWindowProperties::RetreiveMovedOnOpeningProperty(HWND window) +{ + HANDLE handle = ::GetProp(window, ZonedWindowProperties::PropertyMovedOnOpening); + return handle != nullptr; +} + std::optional FancyZonesWindowProperties::GetTabSortKeyWithinZone(HWND window) { auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h index 9563718ff7..6c3c17e37d 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h @@ -10,6 +10,7 @@ namespace ZonedWindowProperties const wchar_t PropertyRestoreSizeID[] = L"FancyZones_RestoreSize"; const wchar_t PropertyRestoreOriginID[] = L"FancyZones_RestoreOrigin"; const wchar_t PropertyCornerPreference[] = L"FancyZones_CornerPreference"; + const wchar_t PropertyMovedOnOpening[] = L"FancyZones_MovedOnOpening"; const wchar_t MultiMonitorName[] = L"FancyZones"; const wchar_t MultiMonitorInstance[] = L"MultiMonitorDevice"; @@ -21,6 +22,9 @@ namespace FancyZonesWindowProperties void RemoveZoneIndexProperty(HWND window); ZoneIndexSet RetrieveZoneIndexProperty(HWND window); + void StampMovedOnOpeningProperty(HWND window); + bool RetreiveMovedOnOpeningProperty(HWND window); + std::optional GetTabSortKeyWithinZone(HWND window); void SetTabSortKeyWithinZone(HWND window, std::optional tabSortKeyWithinZone); } diff --git a/src/modules/fancyzones/FancyZonesLib/Layout.cpp b/src/modules/fancyzones/FancyZonesLib/Layout.cpp new file mode 100644 index 0000000000..ae1c352c0a --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/Layout.cpp @@ -0,0 +1,334 @@ +#include "pch.h" +#include "Layout.h" + +#include +#include +#include +#include +#include + +#include + +namespace ZoneSelectionAlgorithms +{ + constexpr int OVERLAPPING_CENTERS_SENSITIVITY = 75; + + template + ZoneIndexSet ZoneSelectPriority(const ZonesMap& zones, const ZoneIndexSet& capturedZones, CompareF compare) + { + size_t chosen = 0; + + for (size_t i = 1; i < capturedZones.size(); ++i) + { + if (compare(zones.at(capturedZones[i]), zones.at(capturedZones[chosen]))) + { + chosen = i; + } + } + + return { capturedZones[chosen] }; + } + + ZoneIndexSet ZoneSelectSubregion(const ZonesMap& zones, const ZoneIndexSet& capturedZones, POINT pt, int sensitivityRadius) + { + auto expand = [&](RECT& rect) { + rect.top -= sensitivityRadius / 2; + rect.bottom += sensitivityRadius / 2; + rect.left -= sensitivityRadius / 2; + rect.right += sensitivityRadius / 2; + }; + + // Compute the overlapped rectangle. + RECT overlap = zones.at(capturedZones[0]).GetZoneRect(); + expand(overlap); + + for (size_t i = 1; i < capturedZones.size(); ++i) + { + RECT current = zones.at(capturedZones[i]).GetZoneRect(); + expand(current); + + overlap.top = max(overlap.top, current.top); + overlap.left = max(overlap.left, current.left); + overlap.bottom = min(overlap.bottom, current.bottom); + overlap.right = min(overlap.right, current.right); + } + + // Avoid division by zero + int width = max(overlap.right - overlap.left, 1); + int height = max(overlap.bottom - overlap.top, 1); + + bool verticalSplit = height > width; + ZoneIndex zoneIndex; + + if (verticalSplit) + { + zoneIndex = (static_cast(pt.y) - overlap.top) * capturedZones.size() / height; + } + else + { + zoneIndex = (static_cast(pt.x) - overlap.left) * capturedZones.size() / width; + } + + zoneIndex = std::clamp(zoneIndex, ZoneIndex(0), static_cast(capturedZones.size()) - 1); + + return { capturedZones[zoneIndex] }; + } + + ZoneIndexSet ZoneSelectClosestCenter(const ZonesMap& zones, const ZoneIndexSet& capturedZones, POINT pt) + { + auto getCenter = [](auto zone) { + RECT rect = zone.GetZoneRect(); + return POINT{ (rect.right + rect.left) / 2, (rect.top + rect.bottom) / 2 }; + }; + auto pointDifference = [](POINT pt1, POINT pt2) { + return (pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y); + }; + auto distanceFromCenter = [&](auto zone) { + POINT center = getCenter(zone); + return pointDifference(center, pt); + }; + auto closerToCenter = [&](auto zone1, auto zone2) { + if (pointDifference(getCenter(zone1), getCenter(zone2)) > OVERLAPPING_CENTERS_SENSITIVITY) + { + return distanceFromCenter(zone1) < distanceFromCenter(zone2); + } + else + { + return zone1.GetZoneArea() < zone2.GetZoneArea(); + }; + }; + return ZoneSelectPriority(zones, capturedZones, closerToCenter); + } +} + +Layout::Layout(const LayoutData& data) : + m_data(data) +{ +} + +bool Layout::Init(const FancyZonesUtils::Rect& workArea, HMONITOR monitor) noexcept +{ + //invalid work area + if (workArea.width() == 0 || workArea.height() == 0) + { + Logger::error(L"Layout initialization: invalid work area"); + return false; + } + + //invalid zoneCount, may cause division by zero + if (m_data.zoneCount <= 0 && m_data.type != FancyZonesDataTypes::ZoneSetLayoutType::Custom) + { + Logger::error(L"Layout initialization: invalid zone count"); + return false; + } + + switch (m_data.type) + { + case FancyZonesDataTypes::ZoneSetLayoutType::Focus: + m_zones = LayoutConfigurator::Focus(workArea, m_data.zoneCount); + break; + case FancyZonesDataTypes::ZoneSetLayoutType::Columns: + m_zones = LayoutConfigurator::Columns(workArea, m_data.zoneCount, m_data.spacing); + break; + case FancyZonesDataTypes::ZoneSetLayoutType::Rows: + m_zones = LayoutConfigurator::Rows(workArea, m_data.zoneCount, m_data.spacing); + break; + case FancyZonesDataTypes::ZoneSetLayoutType::Grid: + m_zones = LayoutConfigurator::Grid(workArea, m_data.zoneCount, m_data.spacing); + break; + case FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid: + m_zones = LayoutConfigurator::PriorityGrid(workArea, m_data.zoneCount, m_data.spacing); + break; + case FancyZonesDataTypes::ZoneSetLayoutType::Custom: + { + const auto customLayoutData = CustomLayouts::instance().GetCustomLayoutData(m_data.uuid); + if (customLayoutData.has_value()) + { + m_zones = LayoutConfigurator::Custom(workArea, monitor, customLayoutData.value(), m_data.spacing); + } + else + { + Logger::error(L"Custom layout not found"); + return false; + } + } + break; + } + + return m_zones.size() == m_data.zoneCount; +} + +GUID Layout::Id() const noexcept +{ + return m_data.uuid; +} + +FancyZonesDataTypes::ZoneSetLayoutType Layout::Type() const noexcept +{ + return m_data.type; +} + +const ZonesMap& Layout::Zones() const noexcept +{ + return m_zones; +} + +ZoneIndexSet Layout::ZonesFromPoint(POINT pt) const noexcept +{ + ZoneIndexSet capturedZones; + ZoneIndexSet strictlyCapturedZones; + for (const auto& [zoneId, zone] : m_zones) + { + const RECT& zoneRect = zone.GetZoneRect(); + if (zoneRect.left - m_data.sensitivityRadius <= pt.x && pt.x <= zoneRect.right + m_data.sensitivityRadius && + zoneRect.top - m_data.sensitivityRadius <= pt.y && pt.y <= zoneRect.bottom + m_data.sensitivityRadius) + { + capturedZones.emplace_back(zoneId); + } + + if (zoneRect.left <= pt.x && pt.x < zoneRect.right && + zoneRect.top <= pt.y && pt.y < zoneRect.bottom) + { + strictlyCapturedZones.emplace_back(zoneId); + } + } + + // If only one zone is captured, but it's not strictly captured + // don't consider it as captured + if (capturedZones.size() == 1 && strictlyCapturedZones.size() == 0) + { + return {}; + } + + // If captured zones do not overlap, return all of them + // Otherwise, return one of them based on the chosen selection algorithm. + bool overlap = false; + for (size_t i = 0; i < capturedZones.size(); ++i) + { + for (size_t j = i + 1; j < capturedZones.size(); ++j) + { + RECT rectI; + RECT rectJ; + try + { + rectI = m_zones.at(capturedZones[i]).GetZoneRect(); + rectJ = m_zones.at(capturedZones[j]).GetZoneRect(); + } + catch (std::out_of_range) + { + return {}; + } + + if (max(rectI.top, rectJ.top) + m_data.sensitivityRadius < min(rectI.bottom, rectJ.bottom) && + max(rectI.left, rectJ.left) + m_data.sensitivityRadius < min(rectI.right, rectJ.right)) + { + overlap = true; + break; + } + } + if (overlap) + { + break; + } + } + + if (overlap) + { + try + { + using Algorithm = OverlappingZonesAlgorithm; + + switch (FancyZonesSettings::settings().overlappingZonesAlgorithm) + { + case Algorithm::Smallest: + return ZoneSelectionAlgorithms::ZoneSelectPriority(m_zones, capturedZones, [&](auto zone1, auto zone2) { return zone1.GetZoneArea() < zone2.GetZoneArea(); }); + case Algorithm::Largest: + return ZoneSelectionAlgorithms::ZoneSelectPriority(m_zones, capturedZones, [&](auto zone1, auto zone2) { return zone1.GetZoneArea() > zone2.GetZoneArea(); }); + case Algorithm::Positional: + return ZoneSelectionAlgorithms::ZoneSelectSubregion(m_zones, capturedZones, pt, m_data.sensitivityRadius); + case Algorithm::ClosestCenter: + return ZoneSelectionAlgorithms::ZoneSelectClosestCenter(m_zones, capturedZones, pt); + } + } + catch (std::out_of_range) + { + Logger::error("Exception out_of_range was thrown in ZoneSet::ZonesFromPoint"); + return { capturedZones[0] }; + } + } + + return capturedZones; +} + +ZoneIndexSet Layout::GetCombinedZoneRange(const ZoneIndexSet& initialZones, const ZoneIndexSet& finalZones) const noexcept +{ + ZoneIndexSet combinedZones, result; + std::set_union(begin(initialZones), end(initialZones), begin(finalZones), end(finalZones), std::back_inserter(combinedZones)); + + RECT boundingRect; + bool boundingRectEmpty = true; + + for (ZoneIndex zoneId : combinedZones) + { + if (m_zones.contains(zoneId)) + { + const RECT rect = m_zones.at(zoneId).GetZoneRect(); + if (boundingRectEmpty) + { + boundingRect = rect; + boundingRectEmpty = false; + } + else + { + boundingRect.left = min(boundingRect.left, rect.left); + boundingRect.top = min(boundingRect.top, rect.top); + boundingRect.right = max(boundingRect.right, rect.right); + boundingRect.bottom = max(boundingRect.bottom, rect.bottom); + } + } + } + + if (!boundingRectEmpty) + { + for (const auto& [zoneId, zone] : m_zones) + { + const RECT rect = zone.GetZoneRect(); + if (boundingRect.left <= rect.left && rect.right <= boundingRect.right && + boundingRect.top <= rect.top && rect.bottom <= boundingRect.bottom) + { + result.push_back(zoneId); + } + } + } + + return result; +} + +RECT Layout::GetCombinedZonesRect(const ZoneIndexSet& zones) +{ + RECT size{}; + bool sizeEmpty = true; + + for (ZoneIndex id : zones) + { + if (m_zones.contains(id)) + { + const auto& zone = m_zones.at(id); + const RECT newSize = zone.GetZoneRect(); + if (!sizeEmpty) + { + size.left = min(size.left, newSize.left); + size.top = min(size.top, newSize.top); + size.right = max(size.right, newSize.right); + size.bottom = max(size.bottom, newSize.bottom); + } + else + { + size = newSize; + sizeEmpty = false; + } + } + } + + return size; +} diff --git a/src/modules/fancyzones/FancyZonesLib/Layout.h b/src/modules/fancyzones/FancyZonesLib/Layout.h new file mode 100644 index 0000000000..b16671e5d3 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/Layout.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include // ZonesMap + +class Layout +{ +public: + Layout(const LayoutData& data); + ~Layout() = default; + + bool Init(const FancyZonesUtils::Rect& workAreaRect, HMONITOR monitor) noexcept; + + GUID Id() const noexcept; + FancyZonesDataTypes::ZoneSetLayoutType Type() const noexcept; + + const ZonesMap& Zones() const noexcept; + ZoneIndexSet ZonesFromPoint(POINT pt) const noexcept; + /** + * Returns all zones spanned by the minimum bounding rectangle containing the two given zone index sets. + */ + ZoneIndexSet GetCombinedZoneRange(const ZoneIndexSet& initialZones, const ZoneIndexSet& finalZones) const noexcept; + + RECT GetCombinedZonesRect(const ZoneIndexSet& zones); + +private: + const LayoutData m_data; + ZonesMap m_zones{}; +}; diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp new file mode 100644 index 0000000000..63cf330d5d --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp @@ -0,0 +1,199 @@ +#include "pch.h" +#include "LayoutAssignedWindows.h" + +#include +#include +#include +#include + +LayoutAssignedWindows::LayoutAssignedWindows() +{ + m_extendData = std::make_unique(); +} + +void LayoutAssignedWindows::Assign(HWND window, const ZoneIndexSet& zones) +{ + Dismiss(window); + + // clear info about extention + 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); + } + + if (FancyZonesSettings::settings().disableRoundCorners) + { + FancyZonesWindowUtils::DisableRoundCorners(window); + } + + auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window); + InsertWindowIntoZone(window, tabSortKeyWithinZone, zones); +} + +void LayoutAssignedWindows::Dismiss(HWND window) +{ + if (m_windowIndexSet.contains(window)) + { + const auto& indexSet = m_windowIndexSet.at(window); + auto& windows = m_windowsByIndexSets[indexSet]; + windows.erase(find(begin(windows), end(windows), window)); + if (windows.empty()) + { + m_windowsByIndexSets.erase(m_windowIndexSet[window]); + } + + m_windowIndexSet.erase(window); + } + + FancyZonesWindowProperties::SetTabSortKeyWithinZone(window, std::nullopt); +} + +ZoneIndexSet LayoutAssignedWindows::GetZoneIndexSetFromWindow(HWND window) const noexcept +{ + auto it = m_windowIndexSet.find(window); + if (it != m_windowIndexSet.end()) + { + return it->second; + } + + return {}; +} + +bool LayoutAssignedWindows::IsZoneEmpty(ZoneIndex zoneIndex) const noexcept +{ + for (auto& [window, zones] : m_windowIndexSet) + { + if (find(begin(zones), end(zones), zoneIndex) != end(zones)) + { + return false; + } + } + + return true; +} + +void LayoutAssignedWindows::CycleWindows(HWND window, bool reverse) +{ + auto indexSet = GetZoneIndexSetFromWindow(window); + + // Do nothing in case the window is not recognized + if (indexSet.empty()) + { + return; + } + + for (;;) + { + auto next = GetNextZoneWindow(indexSet, window, reverse); + if (!next) + { + break; + } + + // Determine whether the window still exists + if (!IsWindow(next)) + { + // Dismiss the encountered window since it was probably closed + Dismiss(next); + continue; + } + + if (VirtualDesktop::instance().IsWindowOnCurrentDesktop(next)) + { + FancyZonesWindowUtils::SwitchToWindow(next); + } + + break; + } +} + +const std::unique_ptr& LayoutAssignedWindows::ExtendWindowData() +{ + return m_extendData; +} + +void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) +{ + if (tabSortKeyWithinZone.has_value()) + { + // Insert the tab using the provided sort key + auto predicate = [tabSortKeyWithinZone](HWND tab) { + auto currentTabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(tab); + if (currentTabSortKeyWithinZone.has_value()) + { + return currentTabSortKeyWithinZone.value() > tabSortKeyWithinZone; + } + else + { + return false; + } + }; + + auto position = std::find_if(m_windowsByIndexSets[indexSet].begin(), m_windowsByIndexSets[indexSet].end(), predicate); + m_windowsByIndexSets[indexSet].insert(position, window); + } + else + { + // Insert the tab at the end + tabSortKeyWithinZone = 0; + if (!m_windowsByIndexSets[indexSet].empty()) + { + auto prevTab = m_windowsByIndexSets[indexSet].back(); + auto prevTabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(prevTab); + if (prevTabSortKeyWithinZone.has_value()) + { + tabSortKeyWithinZone = prevTabSortKeyWithinZone.value() + 1; + } + } + + m_windowsByIndexSets[indexSet].push_back(window); + } + + FancyZonesWindowProperties::SetTabSortKeyWithinZone(window, tabSortKeyWithinZone); +} + +HWND LayoutAssignedWindows::GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept +{ + if (!m_windowsByIndexSets.contains(indexSet)) + { + return nullptr; + } + + const auto& assignedWindows = m_windowsByIndexSets[indexSet]; + if (assignedWindows.empty()) + { + return nullptr; + } + + auto iter = std::find(assignedWindows.begin(), assignedWindows.end(), current); + if (!reverse) + { + ++iter; + return iter == assignedWindows.end() ? assignedWindows.front() : *iter; + } + else + { + return iter == assignedWindows.begin() ? assignedWindows.back() : *(--iter); + } +} diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h new file mode 100644 index 0000000000..ae7c3d4fd2 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +class LayoutAssignedWindows +{ +public: + struct ExtendWindowModeData + { + std::map windowInitialIndexSet; + std::map windowFinalIndex; + }; + +public : + LayoutAssignedWindows(); + ~LayoutAssignedWindows() = default; + + void Assign(HWND window, const ZoneIndexSet& zones); + void Extend(HWND window, const ZoneIndexSet& zones); + void Dismiss(HWND window); + + ZoneIndexSet GetZoneIndexSetFromWindow(HWND window) const noexcept; + bool IsZoneEmpty(ZoneIndex zoneIndex) const noexcept; + + 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/LayoutConfigurator.cpp b/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.cpp index de834dc2bd..567be4688c 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.cpp +++ b/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.cpp @@ -92,15 +92,15 @@ namespace }; } -bool AddZone(winrt::com_ptr zone, ZonesMap& zones) noexcept +bool AddZone(Zone zone, ZonesMap& zones) noexcept { - auto zoneId = zone->Id(); + auto zoneId = zone.Id(); if (zones.contains(zoneId)) { return false; } - zones[zoneId] = zone; + zones.insert({ zoneId, std::move(zone) }); return true; } @@ -169,8 +169,8 @@ ZonesMap CalculateGridZones(FancyZonesUtils::Rect workArea, FancyZonesDataTypes: left += col == 0 ? spacing : spacing / 2; right -= maxCol == static_cast(gridLayoutInfo.columns()) - 1 ? spacing : spacing / 2; - auto zone = MakeZone(RECT{ left, top, right, bottom }, i); - if (zone) + Zone zone(RECT{ left, top, right, bottom }, i); + if (zone.IsValid()) { if (!AddZone(zone, zones)) { @@ -207,8 +207,8 @@ ZonesMap LayoutConfigurator::Focus(FancyZonesUtils::Rect workArea, int zoneCount for (int i = 0; i < zoneCount; i++) { - auto zone = MakeZone(focusZoneRect, zones.size()); - if (zone) + Zone zone(focusZoneRect, zones.size()); + if (zone.IsValid()) { if (!AddZone(zone, zones)) { @@ -251,8 +251,8 @@ ZonesMap LayoutConfigurator::Rows(FancyZonesUtils::Rect workArea, int zoneCount, right = totalWidth + spacing; bottom = top + (zoneIndex + 1) * totalHeight / zoneCount - zoneIndex * totalHeight / zoneCount; - auto zone = MakeZone(RECT{ left, top, right, bottom }, zones.size()); - if (zone) + Zone zone(RECT{ left, top, right, bottom }, zones.size()); + if (zone.IsValid()) { if (!AddZone(zone, zones)) { @@ -292,8 +292,8 @@ ZonesMap LayoutConfigurator::Columns(FancyZonesUtils::Rect workArea, int zoneCou right = left + (zoneIndex + 1) * totalWidth / zoneCount - zoneIndex * totalWidth / zoneCount; bottom = totalHeight + spacing; - auto zone = MakeZone(RECT{ left, top, right, bottom }, zones.size()); - if (zone) + Zone zone(RECT{ left, top, right, bottom }, zones.size()); + if (zone.IsValid()) { if (!AddZone(zone, zones)) { @@ -404,8 +404,8 @@ ZonesMap LayoutConfigurator::Custom(FancyZonesUtils::Rect workArea, HMONITOR mon DPIAware::Convert(monitor, x, y); DPIAware::Convert(monitor, zoneWidth, zoneHeight); - auto zone = MakeZone(RECT{ static_cast(x), static_cast(y), static_cast(x + zoneWidth), static_cast(y + zoneHeight) }, zones.size()); - if (zone) + Zone zone(RECT{ static_cast(x), static_cast(y), static_cast(x + zoneWidth), static_cast(y + zoneHeight) }, zones.size()); + if (zone.IsValid()) { if (!AddZone(zone, zones)) { diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.h b/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.h index 9abc0243e3..150e9c7746 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.h +++ b/src/modules/fancyzones/FancyZonesLib/LayoutConfigurator.h @@ -4,7 +4,7 @@ #include // Mapping zone id to zone -using ZonesMap = std::map>; +using ZonesMap = std::map; namespace FancyZonesDataTypes { diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp index 32922d5ad1..0a60768a86 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp @@ -132,10 +132,10 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const auto workArea = workAreaMap.find(monitor); if (workArea != workAreaMap.end()) { - const auto zoneSet = workArea->second->ZoneSet(); - if (zoneSet) + const auto& layoutWindows = workArea->second->GetLayoutWindows(); + if (layoutWindows) { - zoneSet->DismissWindow(window); + layoutWindows->Dismiss(window); } } } @@ -207,7 +207,7 @@ void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, } } -void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept +void WindowMoveHandler::MoveSizeEnd(HWND window, const std::unordered_map>& workAreaMap) noexcept { if (window != m_draggedWindow) { @@ -235,7 +235,7 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st } else { - workArea->MoveSizeEnd(m_draggedWindow, ptScreen); + workArea->MoveSizeEnd(m_draggedWindow); } } else @@ -261,13 +261,13 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st if (workArea != workAreaMap.end()) { const auto workAreaPtr = workArea->second; - const auto zoneSet = workAreaPtr->ZoneSet(); - if (zoneSet) + const auto& layout = workAreaPtr->GetLayout(); + if (layout) { - wil::unique_cotaskmem_string guidString; - if (SUCCEEDED_LOG(StringFromCLSID(zoneSet->Id(), &guidString))) + auto guidStr = FancyZonesUtils::GuidToString(layout->Id()); + if (guidStr.has_value()) { - AppZoneHistory::instance().RemoveAppLastZone(window, workAreaPtr->UniqueId(), guidString.get()); + AppZoneHistory::instance().RemoveAppLastZone(window, workAreaPtr->UniqueId(), guidStr.value()); } } } @@ -324,12 +324,10 @@ void WindowMoveHandler::UpdateWindowsPositions(const std::unordered_mapMoveWindowIntoZoneByIndexSet(window, zoneIndexSet); - } + activeWorkAreas.at(monitor)->MoveWindowIntoZoneByIndexSet(window, zoneIndexSet); } } } diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h index 67c4f9c41c..1e3c2a1baa 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h @@ -16,7 +16,7 @@ public: void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; - void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; + void MoveSizeEnd(HWND window, const std::unordered_map>& workAreaMap) noexcept; void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, std::shared_ptr workArea) noexcept; bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, std::shared_ptr workArea) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 6ba6de7524..826340ae18 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -127,12 +127,17 @@ HRESULT WorkArea::MoveSizeEnter(HWND window) noexcept m_highlightZone = {}; m_initialHighlightZone = {}; ShowZonesOverlay(); - Trace::WorkArea::MoveOrResizeStarted(m_zoneSet); + Trace::WorkArea::MoveOrResizeStarted(m_layout.get(), m_layoutWindows.get()); return S_OK; } HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept { + if (!m_layout) + { + return -1; + } + bool redraw = false; POINT ptClient = ptScreen; MapWindowPoints(nullptr, m_window, &ptClient, 1); @@ -150,7 +155,7 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s } else { - highlightZone = m_zoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); + highlightZone = m_layout->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); } } else @@ -167,33 +172,24 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s redraw = true; } - if (redraw) + if (redraw && m_zonesOverlay) { - m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); + m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); } return S_OK; } -HRESULT WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept +HRESULT WorkArea::MoveSizeEnd(HWND window) noexcept { if (m_windowMoveSize != window) { return E_INVALIDARG; } - if (m_zoneSet) - { - POINT ptClient = ptScreen; - MapWindowPoints(nullptr, m_window, &ptClient, 1); - m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); + MoveWindowIntoZoneByIndexSet(window, m_highlightZone); - if (!FancyZonesWindowUtils::HasVisibleOwner(window)) - { - SaveWindowProcessToZoneIndex(window); - } - } - Trace::WorkArea::MoveOrResizeEnd(m_zoneSet); + Trace::WorkArea::MoveOrResizeEnd(m_layout.get(), m_layoutWindows.get()); HideZonesOverlay(); m_windowMoveSize = nullptr; @@ -207,80 +203,264 @@ void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept { - if (m_zoneSet) + if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty()) { - m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); + return; } + + FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); + auto rect = m_layout->GetCombinedZonesRect(indexSet); + auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); + FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); + + m_layoutWindows->Assign(window, indexSet); + FancyZonesWindowProperties::StampZoneIndexProperty(window, indexSet); + + SaveWindowProcessToZoneIndex(window); } bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept { - if (m_zoneSet) + if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) { - if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) + return false; + } + + auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window); + 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 + { + ZoneIndex oldId = zoneIndexes[0]; + + // We reached the edge + if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == numZones - 1)) { - if (!FancyZonesWindowUtils::HasVisibleOwner(window)) + if (!cycle) { - SaveWindowProcessToZoneIndex(window); + 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); } - return true; } } - return false; + + if (!FancyZonesWindowUtils::HasVisibleOwner(window)) + { + SaveWindowProcessToZoneIndex(window); + } + + return true; } bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept { - if (m_zoneSet) + if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) { - if (m_zoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) + return false; + } + + const auto& zones = m_layout->Zones(); + std::vector usedZoneIndices(zones.size(), false); + auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window); + + for (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]); + SaveWindowProcessToZoneIndex(window); + 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); SaveWindowProcessToZoneIndex(window); + Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get()); return true; } } + return false; } bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept { - if (m_zoneSet) + if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) { - if (m_zoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) + 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 (ZoneIndex idx : appliedZones) { - SaveWindowProcessToZoneIndex(window); - return true; + 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); } } + + 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 }); + } + + auto rect = m_layout->GetCombinedZonesRect(resultIndexSet); + auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); + FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); + + m_layoutWindows->Extend(window, resultIndexSet); + FancyZonesWindowProperties::StampZoneIndexProperty(window, resultIndexSet); + + SaveWindowProcessToZoneIndex(window); + + return true; + } + return false; } void WorkArea::SaveWindowProcessToZoneIndex(HWND window) noexcept { - if (m_zoneSet) + if (m_layout && m_layoutWindows) { - auto zoneIndexSet = m_zoneSet->GetZoneIndexSetFromWindow(window); + auto zoneIndexSet = m_layoutWindows->GetZoneIndexSetFromWindow(window); if (zoneIndexSet.size()) { - OLECHAR* guidString; - if (StringFromCLSID(m_zoneSet->Id(), &guidString) == S_OK) + auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); + if (guidStr.has_value()) { - AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet); + AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zoneIndexSet); } - - CoTaskMemFree(guidString); } } } ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept { - if (m_zoneSet) + if (m_layout) { - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED(StringFromCLSID(m_zoneSet->Id(), &zoneSetId))) + auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); + if (guidStr.has_value()) { - return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, zoneSetId.get()); + return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value()); } else { @@ -297,10 +477,10 @@ ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept void WorkArea::ShowZonesOverlay() noexcept { - if (m_window) + if (m_window && m_layout) { SetAsTopmostWindow(); - m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); + m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); m_zonesOverlay->Show(); } } @@ -324,37 +504,37 @@ void WorkArea::UpdateActiveZoneSet() noexcept AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); } - CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm); - if (m_window && m_zoneSet) + CalculateZoneSet(); + if (m_window && m_layout) { m_highlightZone.clear(); - m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); + m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); } } -void WorkArea::CycleTabs(HWND window, bool reverse) noexcept +void WorkArea::CycleWindows(HWND window, bool reverse) noexcept { - if (m_zoneSet) + if (m_layoutWindows) { - m_zoneSet->CycleTabs(window, reverse); + m_layoutWindows->CycleWindows(window, reverse); } } void WorkArea::ClearSelectedZones() noexcept { - if (m_highlightZone.size()) + if (m_highlightZone.size() && m_layout) { m_highlightZone.clear(); - m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); + m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); } } void WorkArea::FlashZones() noexcept { - if (m_window) + if (m_window && m_layout) { SetAsTopmostWindow(); - m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); + m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); m_zonesOverlay->Flash(); } } @@ -391,36 +571,25 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) } } - CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm); + CalculateZoneSet(); } -void WorkArea::CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept +void WorkArea::CalculateZoneSet() noexcept { const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId); if (!appliedLayout.has_value()) { - Logger::error(L"Layout wasn't applied. Can't init zone set"); + Logger::error(L"Layout wasn't applied. Can't init layout on work area {}x{}", m_workAreaRect.width(), m_workAreaRect.height()); return; } - auto zoneSet = MakeZoneSet(ZoneSetConfig( - appliedLayout->uuid, - appliedLayout->type, - m_monitor, - appliedLayout->sensitivityRadius, - overlappingAlgorithm)); + m_layout = std::make_unique(appliedLayout.value()); + m_layout->Init(m_workAreaRect, m_monitor); - bool showSpacing = appliedLayout->showSpacing; - int spacing = showSpacing ? appliedLayout->spacing : 0; - int zoneCount = appliedLayout->zoneCount; - - zoneSet->CalculateZones(m_workAreaRect, zoneCount, spacing); - UpdateActiveZoneSet(zoneSet.get()); -} - -void WorkArea::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept -{ - m_zoneSet.copy_from(zoneSet); + if (!m_layoutWindows) + { + m_layoutWindows = std::make_unique(); + } } LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept @@ -447,10 +616,11 @@ LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept ZoneIndexSet WorkArea::ZonesFromPoint(POINT pt) noexcept { - if (m_zoneSet) + if (m_layout) { - return m_zoneSet->ZonesFromPoint(pt); + return m_layout->ZonesFromPoint(pt); } + return {}; } diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 61ee504887..94fd46db1c 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -1,7 +1,8 @@ #pragma once #include -#include +#include +#include #include class ZonesOverlay; @@ -55,13 +56,14 @@ public: } FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; } - IZoneSet* ZoneSet() const noexcept { return m_zoneSet.get(); } + const std::unique_ptr& GetLayout() const noexcept { return m_layout; } + const std::unique_ptr& GetLayoutWindows() const noexcept { return m_layoutWindows; } ZoneIndexSet GetWindowZoneIndexes(HWND window) const noexcept; HRESULT MoveSizeEnter(HWND window) noexcept; HRESULT MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept; - HRESULT MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; + HRESULT MoveSizeEnd(HWND window) noexcept; void MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept; void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept; bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; @@ -76,7 +78,7 @@ public: void FlashZones() noexcept; void ClearSelectedZones() noexcept; - void CycleTabs(HWND window, bool reverse) noexcept; + void CycleWindows(HWND window, bool reverse) noexcept; void LogInitializationError(); @@ -86,8 +88,7 @@ protected: private: bool InitWindow(HINSTANCE hinstance) noexcept; void InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) noexcept; - void CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept; - void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; + void CalculateZoneSet() noexcept; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; ZoneIndexSet ZonesFromPoint(POINT pt) noexcept; void SetAsTopmostWindow() noexcept; @@ -97,7 +98,8 @@ private: const FancyZonesDataTypes::WorkAreaId m_uniqueId; HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. HWND m_windowMoveSize{}; - winrt::com_ptr m_zoneSet; + std::unique_ptr m_layout; + std::unique_ptr m_layoutWindows; ZoneIndexSet m_initialHighlightZone; ZoneIndexSet m_highlightZone; WPARAM m_keyLast{}; diff --git a/src/modules/fancyzones/FancyZonesLib/Zone.cpp b/src/modules/fancyzones/FancyZonesLib/Zone.cpp index 9306e0fafa..c57de1f8bc 100644 --- a/src/modules/fancyzones/FancyZonesLib/Zone.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Zone.cpp @@ -1,54 +1,45 @@ #include "pch.h" - - -#include - -#include -#include #include "Zone.h" -#include "Settings.h" -#include "util.h" -namespace +Zone::Zone(const RECT& zoneRect, const ZoneIndex zoneIndex) : + m_rect(zoneRect), + m_index(zoneIndex) { - bool ValidateZoneRect(const RECT& rect) - { - int width = rect.right - rect.left; - int height = rect.bottom - rect.top; - return rect.left >= ZoneConstants::MAX_NEGATIVE_SPACING && - rect.right >= ZoneConstants::MAX_NEGATIVE_SPACING && - rect.top >= ZoneConstants::MAX_NEGATIVE_SPACING && - rect.bottom >= ZoneConstants::MAX_NEGATIVE_SPACING && - width >= 0 && height >= 0; - } } -struct Zone : winrt::implements +Zone::Zone(const Zone& other) : + m_rect(other.m_rect), + m_index(other.m_index) { -public: - Zone(RECT zoneRect, const ZoneIndex zoneId) : - m_zoneRect(zoneRect), - m_id(zoneId) - { - } - - IFACEMETHODIMP_(RECT) GetZoneRect() const noexcept { return m_zoneRect; } - IFACEMETHODIMP_(long) GetZoneArea() const noexcept { return max(m_zoneRect.bottom - m_zoneRect.top, 0) * max(m_zoneRect.right - m_zoneRect.left, 0); } - IFACEMETHODIMP_(ZoneIndex) Id() const noexcept { return m_id; } - -private: - RECT m_zoneRect{}; - const ZoneIndex m_id{}; -}; - -winrt::com_ptr MakeZone(const RECT& zoneRect, const ZoneIndex zoneId) noexcept -{ - if (ValidateZoneRect(zoneRect) && zoneId >= 0) - { - return winrt::make_self(zoneRect, zoneId); - } - else - { - return nullptr; - } } + +ZoneIndex Zone::Id() const noexcept +{ + return m_index; +} + +bool Zone::IsValid() const noexcept +{ + return m_index >= 0 && isValid(); +} + +RECT Zone::GetZoneRect() const noexcept +{ + return m_rect; +} + +long Zone::GetZoneArea() const noexcept +{ + return max(m_rect.bottom - m_rect.top, 0) * max(m_rect.right - m_rect.left, 0); +} + +bool Zone::isValid() const noexcept +{ + int width = m_rect.right - m_rect.left; + int height = m_rect.bottom - m_rect.top; + return m_rect.left >= ZoneConstants::MAX_NEGATIVE_SPACING && + m_rect.right >= ZoneConstants::MAX_NEGATIVE_SPACING && + m_rect.top >= ZoneConstants::MAX_NEGATIVE_SPACING && + m_rect.bottom >= ZoneConstants::MAX_NEGATIVE_SPACING && + width >= 0 && height >= 0; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/Zone.h b/src/modules/fancyzones/FancyZonesLib/Zone.h index be1081610c..6c88d6facd 100644 --- a/src/modules/fancyzones/FancyZonesLib/Zone.h +++ b/src/modules/fancyzones/FancyZonesLib/Zone.h @@ -11,20 +11,21 @@ using ZoneIndexSet = std::vector; /** * Class representing one zone inside applied zone layout, which is basically wrapper around rectangle structure. */ -interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : public IUnknown +class Zone { - /** - * @returns Zone coordinates (top-left and bottom-right corner) represented as RECT structure. - */ - IFACEMETHOD_(RECT, GetZoneRect)() const = 0; - /** - * @returns Zone area calculated from zone rect - */ - IFACEMETHOD_(long, GetZoneArea)() const = 0; - /** - * @returns Zone identifier. - */ - IFACEMETHOD_(ZoneIndex, Id)() const = 0; -}; +public: + Zone(const RECT& zoneRect, const ZoneIndex zoneIndex); + Zone(const Zone& other); + ~Zone() = default; -winrt::com_ptr MakeZone(const RECT& zoneRect, const ZoneIndex zoneId) noexcept; + ZoneIndex Id() const noexcept; + bool IsValid() const noexcept; + RECT GetZoneRect() const noexcept; + long GetZoneArea() const noexcept; + +private: + const RECT m_rect; + const ZoneIndex m_index; + + bool isValid() const noexcept; +}; diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp deleted file mode 100644 index 04f60deccf..0000000000 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp +++ /dev/null @@ -1,770 +0,0 @@ -#include "pch.h" - -#include "ZoneSet.h" - -#include -#include -#include - -#include -#include - -using namespace FancyZonesUtils; - -namespace -{ - constexpr int OVERLAPPING_CENTERS_SENSITIVITY = 75; -} - -struct ZoneSet : winrt::implements -{ -public: - ZoneSet(ZoneSetConfig const& config) : - m_config(config) - { - } - - ZoneSet(ZoneSetConfig const& config, ZonesMap zones) : - m_config(config), - m_zones(zones) - { - } - - IFACEMETHODIMP_(GUID) - Id() const noexcept { return m_config.Id; } - IFACEMETHODIMP_(FancyZonesDataTypes::ZoneSetLayoutType) - LayoutType() const noexcept { return m_config.LayoutType; } - IFACEMETHODIMP_(ZoneIndexSet) ZonesFromPoint(POINT pt) const noexcept; - IFACEMETHODIMP_(ZoneIndexSet) GetZoneIndexSetFromWindow(HWND window) const noexcept; - IFACEMETHODIMP_(ZonesMap) GetZones()const noexcept override { return m_zones; } - IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex index) noexcept; - IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) noexcept; - IFACEMETHODIMP_(bool) - MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept; - IFACEMETHODIMP_(bool) - MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept; - IFACEMETHODIMP_(bool) - ExtendWindowByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode) noexcept; - IFACEMETHODIMP_(void) - MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptClient) noexcept; - IFACEMETHODIMP_(void) - DismissWindow(HWND window) noexcept; - IFACEMETHODIMP_(void) - CycleTabs(HWND window, bool reverse) noexcept; - IFACEMETHODIMP_(bool) - CalculateZones(FancyZonesUtils::Rect workArea, int zoneCount, int spacing) noexcept; - IFACEMETHODIMP_(bool) IsZoneEmpty(ZoneIndex zoneIndex) const noexcept; - IFACEMETHODIMP_(ZoneIndexSet) GetCombinedZoneRange(const ZoneIndexSet& initialZones, const ZoneIndexSet& finalZones) const noexcept; - -private: - HWND GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept; - void InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet); - ZoneIndexSet ZoneSelectSubregion(const ZoneIndexSet& capturedZones, POINT pt) const; - ZoneIndexSet ZoneSelectClosestCenter(const ZoneIndexSet& capturedZones, POINT pt) const; - - // `compare` should return true if the first argument is a better choice than the second argument. - template - ZoneIndexSet ZoneSelectPriority(const ZoneIndexSet& capturedZones, CompareF compare) const; - - ZonesMap m_zones; - std::map m_windowIndexSet; - std::map> m_windowsByIndexSets; - - // Needed for ExtendWindowByDirectionAndPosition - std::map m_windowInitialIndexSet; - std::map m_windowFinalIndex; - bool m_inExtendWindow = false; - - ZoneSetConfig m_config; -}; - -IFACEMETHODIMP_(ZoneIndexSet) -ZoneSet::ZonesFromPoint(POINT pt) const noexcept -{ - ZoneIndexSet capturedZones; - ZoneIndexSet strictlyCapturedZones; - for (const auto& [zoneId, zone] : m_zones) - { - const RECT& zoneRect = zone->GetZoneRect(); - if (zoneRect.left - m_config.SensitivityRadius <= pt.x && pt.x <= zoneRect.right + m_config.SensitivityRadius && - zoneRect.top - m_config.SensitivityRadius <= pt.y && pt.y <= zoneRect.bottom + m_config.SensitivityRadius) - { - capturedZones.emplace_back(zoneId); - } - - if (zoneRect.left <= pt.x && pt.x < zoneRect.right && - zoneRect.top <= pt.y && pt.y < zoneRect.bottom) - { - strictlyCapturedZones.emplace_back(zoneId); - } - } - - // If only one zone is captured, but it's not strictly captured - // don't consider it as captured - if (capturedZones.size() == 1 && strictlyCapturedZones.size() == 0) - { - return {}; - } - - // If captured zones do not overlap, return all of them - // Otherwise, return one of them based on the chosen selection algorithm. - bool overlap = false; - for (size_t i = 0; i < capturedZones.size(); ++i) - { - for (size_t j = i + 1; j < capturedZones.size(); ++j) - { - RECT rectI; - RECT rectJ; - try - { - rectI = m_zones.at(capturedZones[i])->GetZoneRect(); - rectJ = m_zones.at(capturedZones[j])->GetZoneRect(); - } - catch (std::out_of_range) - { - return {}; - } - - if (max(rectI.top, rectJ.top) + m_config.SensitivityRadius < min(rectI.bottom, rectJ.bottom) && - max(rectI.left, rectJ.left) + m_config.SensitivityRadius < min(rectI.right, rectJ.right)) - { - overlap = true; - break; - } - } - if (overlap) - { - break; - } - } - - if (overlap) - { - try - { - using Algorithm = OverlappingZonesAlgorithm; - - switch (m_config.SelectionAlgorithm) - { - case Algorithm::Smallest: - return ZoneSelectPriority(capturedZones, [&](auto zone1, auto zone2) { return zone1->GetZoneArea() < zone2->GetZoneArea(); }); - case Algorithm::Largest: - return ZoneSelectPriority(capturedZones, [&](auto zone1, auto zone2) { return zone1->GetZoneArea() > zone2->GetZoneArea(); }); - case Algorithm::Positional: - return ZoneSelectSubregion(capturedZones, pt); - case Algorithm::ClosestCenter: - return ZoneSelectClosestCenter(capturedZones, pt); - } - } - catch (std::out_of_range) - { - Logger::error("Exception out_of_range was thrown in ZoneSet::ZonesFromPoint"); - return { capturedZones[0] }; - } - } - - return capturedZones; -} - -ZoneIndexSet ZoneSet::GetZoneIndexSetFromWindow(HWND window) const noexcept -{ - auto it = m_windowIndexSet.find(window); - if (it == m_windowIndexSet.end()) - { - return {}; - } - else - { - return it->second; - } -} - -IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex index) noexcept -{ - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { index }); -} - -IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& zoneIds) noexcept -{ - if (m_zones.empty()) - { - return; - } - - if (!zoneIds.empty()) - { - Logger::trace(L"Move window into zones {} - {}", zoneIds.front(), zoneIds.back()); - } - - // Always clear the info related to SelectManyZones if it's not being used - if (!m_inExtendWindow) - { - m_windowFinalIndex.erase(window); - m_windowInitialIndexSet.erase(window); - } - - auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window); - DismissWindow(window); - - RECT size; - bool sizeEmpty = true; - auto& indexSet = m_windowIndexSet[window]; - - for (ZoneIndex id : zoneIds) - { - if (m_zones.contains(id)) - { - const auto& zone = m_zones.at(id); - const RECT newSize = zone->GetZoneRect(); - if (!sizeEmpty) - { - size.left = min(size.left, newSize.left); - size.top = min(size.top, newSize.top); - size.right = max(size.right, newSize.right); - size.bottom = max(size.bottom, newSize.bottom); - } - else - { - size = newSize; - sizeEmpty = false; - } - - indexSet.push_back(id); - } - } - - if (!sizeEmpty) - { - FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); - - auto rect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, size, workAreaWindow); - FancyZonesWindowUtils::SizeWindowToRect(window, rect); - - if (FancyZonesSettings::settings().disableRoundCorners) - { - FancyZonesWindowUtils::DisableRoundCorners(window); - } - - FancyZonesWindowProperties::StampZoneIndexProperty(window, indexSet); - InsertTabIntoZone(window, tabSortKeyWithinZone, indexSet); - } -} - -IFACEMETHODIMP_(bool) -ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept -{ - if (m_zones.empty()) - { - return false; - } - - auto indexSet = GetZoneIndexSetFromWindow(window); - auto numZones = m_zones.size(); - - // The window was not assigned to any zone here - if (indexSet.size() == 0) - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0); - return true; - } - - ZoneIndex oldId = indexSet[0]; - - // We reached the edge - if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == numZones - 1)) - { - if (!cycle) - { - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, {}); - return false; - } - else - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0); - return true; - } - } - - // We didn't reach the edge - if (vkCode == VK_LEFT) - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId - 1); - } - else - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId + 1); - } - return true; -} - -IFACEMETHODIMP_(bool) -ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept -{ - if (m_zones.empty()) - { - return false; - } - - std::vector usedZoneIndices(m_zones.size(), false); - for (ZoneIndex id : GetZoneIndexSetFromWindow(window)) - { - usedZoneIndices[id] = true; - } - - std::vector zoneRects; - ZoneIndexSet freeZoneIndices; - - for (const auto& [zoneId, zone] : m_zones) - { - if (!usedZoneIndices[zoneId]) - { - zoneRects.emplace_back(m_zones[zoneId]->GetZoneRect()); - freeZoneIndices.emplace_back(zoneId); - } - } - - RECT windowRect, workAreaRect; - if (GetWindowRect(window, &windowRect) && GetWindowRect(workAreaWindow, &workAreaRect)) - { - // Move to coordinates relative to windowZone - windowRect.top -= workAreaRect.top; - windowRect.bottom -= workAreaRect.top; - windowRect.left -= workAreaRect.left; - windowRect.right -= workAreaRect.left; - - auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, freeZoneIndices[result]); - 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(m_zones.size()); - std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone.second->GetZoneRect(); }); - windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, workAreaRect, vkCode); - result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, workAreaWindow, result); - return true; - } - } - } - else - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - } - - return false; -} - -IFACEMETHODIMP_(bool) -ZoneSet::ExtendWindowByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode) noexcept -{ - if (m_zones.empty()) - { - return false; - } - - RECT windowRect, windowZoneRect; - if (GetWindowRect(window, &windowRect) && GetWindowRect(workAreaWindow, &windowZoneRect)) - { - auto oldZones = GetZoneIndexSetFromWindow(window); - std::vector usedZoneIndices(m_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 = m_windowFinalIndex.find(window); - if (finalIndexIt != m_windowFinalIndex.end()) - { - usedZoneIndices[finalIndexIt->second] = true; - windowRect = m_zones[finalIndexIt->second]->GetZoneRect(); - } - else - { - for (ZoneIndex idx : oldZones) - { - usedZoneIndices[idx] = true; - } - // Move to coordinates relative to windowZone - windowRect.top -= windowZoneRect.top; - windowRect.bottom -= windowZoneRect.top; - windowRect.left -= windowZoneRect.left; - windowRect.right -= windowZoneRect.left; - } - - for (size_t i = 0; i < m_zones.size(); i++) - { - if (!usedZoneIndices[i]) - { - zoneRects.emplace_back(m_zones[i]->GetZoneRect()); - freeZoneIndices.emplace_back(i); - } - } - - 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 == m_windowFinalIndex.end()) - { - // Already zoned? - if (oldZones.size()) - { - m_windowInitialIndexSet[window] = oldZones; - m_windowFinalIndex[window] = targetZone; - resultIndexSet = GetCombinedZoneRange(oldZones, { targetZone }); - } - else - { - m_windowInitialIndexSet[window] = { targetZone }; - m_windowFinalIndex[window] = targetZone; - resultIndexSet = { targetZone }; - } - } - else - { - auto deletethis = m_windowInitialIndexSet[window]; - m_windowFinalIndex[window] = targetZone; - resultIndexSet = GetCombinedZoneRange(m_windowInitialIndexSet[window], { targetZone }); - } - - m_inExtendWindow = true; - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, resultIndexSet); - m_inExtendWindow = false; - return true; - } - } - else - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - } - - return false; -} - -IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptClient) noexcept -{ - const auto& zones = ZonesFromPoint(ptClient); - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, zones); -} - -void ZoneSet::DismissWindow(HWND window) noexcept -{ - auto& indexSet = m_windowIndexSet[window]; - if (!indexSet.empty()) - { - auto& windows = m_windowsByIndexSets[indexSet]; - windows.erase(find(begin(windows), end(windows), window)); - if (windows.empty()) - { - m_windowsByIndexSets.erase(indexSet); - } - - indexSet.clear(); - } - - FancyZonesWindowProperties::SetTabSortKeyWithinZone(window, std::nullopt); -} - -IFACEMETHODIMP_(void) -ZoneSet::CycleTabs(HWND window, bool reverse) noexcept -{ - auto indexSet = GetZoneIndexSetFromWindow(window); - - // Do nothing in case the window is not recognized - if (indexSet.empty()) - { - return; - } - - for (;;) - { - auto next = GetNextTab(indexSet, window, reverse); - - // Determine whether the window still exists - if (!IsWindow(next)) - { - // Dismiss the encountered window since it was probably closed - DismissWindow(next); - continue; - } - - FancyZonesWindowUtils::SwitchToWindow(next); - - break; - } -} - -HWND ZoneSet::GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept -{ - const auto& tabs = m_windowsByIndexSets[indexSet]; - auto tabIt = std::find(tabs.begin(), tabs.end(), current); - if (!reverse) - { - ++tabIt; - return tabIt == tabs.end() ? tabs.front() : *tabIt; - } - else - { - return tabIt == tabs.begin() ? tabs.back() : *(--tabIt); - } -} - -void ZoneSet::InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) -{ - if (tabSortKeyWithinZone.has_value()) - { - // Insert the tab using the provided sort key - auto predicate = [tabSortKeyWithinZone](HWND tab) { - auto currentTabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(tab); - if (currentTabSortKeyWithinZone.has_value()) - { - return currentTabSortKeyWithinZone.value() > tabSortKeyWithinZone; - } - else - { - return false; - } - }; - - auto position = std::find_if(m_windowsByIndexSets[indexSet].begin(), m_windowsByIndexSets[indexSet].end(), predicate); - m_windowsByIndexSets[indexSet].insert(position, window); - } - else - { - // Insert the tab at the end - tabSortKeyWithinZone = 0; - if (!m_windowsByIndexSets[indexSet].empty()) - { - auto prevTab = m_windowsByIndexSets[indexSet].back(); - auto prevTabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(prevTab); - if (prevTabSortKeyWithinZone.has_value()) - { - tabSortKeyWithinZone = prevTabSortKeyWithinZone.value() + 1; - } - } - - m_windowsByIndexSets[indexSet].push_back(window); - } - - FancyZonesWindowProperties::SetTabSortKeyWithinZone(window, tabSortKeyWithinZone); -} - -IFACEMETHODIMP_(bool) -ZoneSet::CalculateZones(FancyZonesUtils::Rect workAreaRect, int zoneCount, int spacing) noexcept -{ - Rect workArea(workAreaRect); - //invalid work area - if (workArea.width() == 0 || workArea.height() == 0) - { - Logger::error(L"CalculateZones: invalid work area"); - return false; - } - - //invalid zoneCount, may cause division by zero - if (zoneCount <= 0 && m_config.LayoutType != FancyZonesDataTypes::ZoneSetLayoutType::Custom) - { - Logger::error(L"CalculateZones: invalid zone count"); - return false; - } - - switch (m_config.LayoutType) - { - case FancyZonesDataTypes::ZoneSetLayoutType::Focus: - m_zones = LayoutConfigurator::Focus(workArea, zoneCount); - break; - case FancyZonesDataTypes::ZoneSetLayoutType::Columns: - m_zones = LayoutConfigurator::Columns(workArea, zoneCount, spacing); - break; - case FancyZonesDataTypes::ZoneSetLayoutType::Rows: - m_zones = LayoutConfigurator::Rows(workArea, zoneCount, spacing); - break; - case FancyZonesDataTypes::ZoneSetLayoutType::Grid: - m_zones = LayoutConfigurator::Grid(workArea, zoneCount, spacing); - break; - case FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid: - m_zones = LayoutConfigurator::PriorityGrid(workArea, zoneCount, spacing); - break; - case FancyZonesDataTypes::ZoneSetLayoutType::Custom: - { - const auto zoneSetSearchResult = CustomLayouts::instance().GetCustomLayoutData(m_config.Id); - if (zoneSetSearchResult.has_value()) - { - m_zones = LayoutConfigurator::Custom(workArea, m_config.Monitor, zoneSetSearchResult.value(), spacing); - } - else - { - Logger::error(L"Custom layout not found"); - return false; - } - } - break; - } - - return m_zones.size() == zoneCount; -} - -bool ZoneSet::IsZoneEmpty(ZoneIndex zoneIndex) const noexcept -{ - for (auto& [window, zones] : m_windowIndexSet) - { - if (find(begin(zones), end(zones), zoneIndex) != end(zones)) - { - return false; - } - } - - return true; -} - -ZoneIndexSet ZoneSet::GetCombinedZoneRange(const ZoneIndexSet& initialZones, const ZoneIndexSet& finalZones) const noexcept -{ - ZoneIndexSet combinedZones, result; - std::set_union(begin(initialZones), end(initialZones), begin(finalZones), end(finalZones), std::back_inserter(combinedZones)); - - RECT boundingRect; - bool boundingRectEmpty = true; - - for (ZoneIndex zoneId : combinedZones) - { - if (m_zones.contains(zoneId)) - { - const RECT rect = m_zones.at(zoneId)->GetZoneRect(); - if (boundingRectEmpty) - { - boundingRect = rect; - boundingRectEmpty = false; - } - else - { - boundingRect.left = min(boundingRect.left, rect.left); - boundingRect.top = min(boundingRect.top, rect.top); - boundingRect.right = max(boundingRect.right, rect.right); - boundingRect.bottom = max(boundingRect.bottom, rect.bottom); - } - } - } - - if (!boundingRectEmpty) - { - for (const auto& [zoneId, zone] : m_zones) - { - const RECT rect = zone->GetZoneRect(); - if (boundingRect.left <= rect.left && rect.right <= boundingRect.right && - boundingRect.top <= rect.top && rect.bottom <= boundingRect.bottom) - { - result.push_back(zoneId); - } - } - } - - return result; -} - -ZoneIndexSet ZoneSet::ZoneSelectSubregion(const ZoneIndexSet& capturedZones, POINT pt) const -{ - auto expand = [&](RECT& rect) { - rect.top -= m_config.SensitivityRadius / 2; - rect.bottom += m_config.SensitivityRadius / 2; - rect.left -= m_config.SensitivityRadius / 2; - rect.right += m_config.SensitivityRadius / 2; - }; - - // Compute the overlapped rectangle. - RECT overlap = m_zones.at(capturedZones[0])->GetZoneRect(); - expand(overlap); - - for (size_t i = 1; i < capturedZones.size(); ++i) - { - RECT current = m_zones.at(capturedZones[i])->GetZoneRect(); - expand(current); - - overlap.top = max(overlap.top, current.top); - overlap.left = max(overlap.left, current.left); - overlap.bottom = min(overlap.bottom, current.bottom); - overlap.right = min(overlap.right, current.right); - } - - // Avoid division by zero - int width = max(overlap.right - overlap.left, 1); - int height = max(overlap.bottom - overlap.top, 1); - - bool verticalSplit = height > width; - ZoneIndex zoneIndex; - - if (verticalSplit) - { - zoneIndex = (static_cast(pt.y) - overlap.top) * capturedZones.size() / height; - } - else - { - zoneIndex = (static_cast(pt.x) - overlap.left) * capturedZones.size() / width; - } - - zoneIndex = std::clamp(zoneIndex, ZoneIndex(0), static_cast(capturedZones.size()) - 1); - - return { capturedZones[zoneIndex] }; -} - -ZoneIndexSet ZoneSet::ZoneSelectClosestCenter(const ZoneIndexSet& capturedZones, POINT pt) const -{ - auto getCenter = [](auto zone) { - RECT rect = zone->GetZoneRect(); - return POINT{ (rect.right + rect.left) / 2, (rect.top + rect.bottom) / 2 }; - }; - auto pointDifference = [](POINT pt1, POINT pt2) { - return (pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y); - }; - auto distanceFromCenter = [&](auto zone) { - POINT center = getCenter(zone); - return pointDifference(center, pt); - }; - auto closerToCenter = [&](auto zone1, auto zone2) { - if (pointDifference(getCenter(zone1), getCenter(zone2)) > OVERLAPPING_CENTERS_SENSITIVITY) - { - return distanceFromCenter(zone1) < distanceFromCenter(zone2); - } - else - { - return zone1->GetZoneArea() < zone2->GetZoneArea(); - }; - }; - return ZoneSelectPriority(capturedZones, closerToCenter); -} - -template -ZoneIndexSet ZoneSet::ZoneSelectPriority(const ZoneIndexSet& capturedZones, CompareF compare) const -{ - size_t chosen = 0; - - for (size_t i = 1; i < capturedZones.size(); ++i) - { - if (compare(m_zones.at(capturedZones[i]), m_zones.at(capturedZones[chosen]))) - { - chosen = i; - } - } - - return { capturedZones[chosen] }; -} - -winrt::com_ptr MakeZoneSet(ZoneSetConfig const& config) noexcept -{ - return winrt::make_self(config); -} - diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h deleted file mode 100644 index b6fc6bf1ec..0000000000 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -#include -#include "Settings.h" -#include "util.h" - -namespace FancyZonesDataTypes -{ - enum class ZoneSetLayoutType; -} -/** - * Class representing single zone layout. ZoneSet is responsible for actual calculation of rectangle coordinates - * (whether is grid or canvas layout) and moving windows through them. - */ -interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : public IUnknown -{ - /** - * @returns Unique identifier of zone layout. - */ - IFACEMETHOD_(GUID, Id)() const = 0; - /** - * @returns Type of the zone layout. Layout type can be focus, columns, rows, grid, priority grid or custom. - */ - IFACEMETHOD_(FancyZonesDataTypes::ZoneSetLayoutType, LayoutType)() const = 0; - /** - * Get zones from cursor coordinates. - * - * @param pt Cursor coordinates. - * @returns Vector of indices, corresponding to the current set of zones - the zones considered active. - */ - IFACEMETHOD_(ZoneIndexSet, ZonesFromPoint)(POINT pt) const = 0; - /** - * Get index set of the zones to which the window was assigned. - * - * @param window Handle of the window. - * @returns A vector of ZoneIndex, 0-based index set. - */ - IFACEMETHOD_(ZoneIndexSet, GetZoneIndexSetFromWindow)(HWND window) const = 0; - /** - * @returns Array of zone objects (defining coordinates of the zone) inside this zone layout. - */ - IFACEMETHOD_(ZonesMap, GetZones) () const = 0; - /** - * Assign window to the zone based on zone index inside zone layout. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param index Zone index within zone layout. - */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND workAreaWindow, ZoneIndex index) = 0; - /** - * Assign window to the zones based on the set of zone indices inside zone layout. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param indexSet The set of zone indices within zone layout. - */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) = 0; - /** - * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow), based on zone index numbers, - * not their on-screen position. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param vkCode Pressed arrow key. - * @param cycle Whether we should move window to the first zone if we reached last zone in layout. - * - * @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more - * zones left in the zone layout in which window can move. - */ - IFACEMETHOD_(bool, MoveWindowIntoZoneByDirectionAndIndex)(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) = 0; - /** - * Assign window to the zone based on direction (using WIN + LEFT/RIGHT/UP/DOWN arrow), based on - * their on-screen position. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param vkCode Pressed arrow key. - * @param cycle Whether we should move window to the first zone if we reached last zone in layout. - * - * @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more - * zones left in the zone layout in which window can move. - */ - IFACEMETHOD_(bool, MoveWindowIntoZoneByDirectionAndPosition) - (HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) = 0; - /** - * Extend or shrink the window to an adjacent zone based on direction (using CTRL+WIN+ALT + LEFT/RIGHT/UP/DOWN arrow), based on - * their on-screen position. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param vkCode Pressed arrow key. - * - * @returns Boolean indicating whether the window was rezoned. False could be returned when there are no more - * zones available in the given direction. - */ - IFACEMETHOD_(bool, ExtendWindowByDirectionAndPosition) - (HWND window, HWND workAreaWindow, DWORD vkCode) = 0; - /** - * Assign window to the zone based on cursor coordinates. - * - * @param window Handle of window which should be assigned to zone. - * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @param pt Cursor coordinates. - */ - IFACEMETHOD_(void, MoveWindowIntoZoneByPoint) - (HWND window, HWND workAreaWindow, POINT ptClient) = 0; - /** - * Dismiss window from zone. - * - * @param window Handle of window which should be dismissed from zone. - */ - IFACEMETHOD_(void, DismissWindow) - (HWND window) = 0; - /** - * Cycle through tabs in the zone that the window is in. - * - * @param window Handle of window which is cycled from (the current tab). - * @param reverse Whether to cycle in reverse order (to the previous tab) or to move to the next tab. - */ - IFACEMETHOD_(void, CycleTabs) - (HWND window, bool reverse) = 0; - /** - * Calculate zone coordinates within zone layout based on number of zones and spacing. - * - * @param workAreaRect The rectangular area on the screen on which the zone layout is applied. - * @param zoneCount Number of zones inside zone layout. - * @param spacing Spacing between zones in pixels. - * - * @returns Boolean indicating if calculation was successful. - */ - IFACEMETHOD_(bool, CalculateZones)(FancyZonesUtils::Rect workAreaRect, int zoneCount, int spacing) = 0; - /** - * Check if the zone with the specified index is empty. Returns true if the zone with passed zoneIndex does not exist. - * - * @param zoneIndex The index of of the zone within this zone set. - * - * @returns Boolean indicating whether the zone is empty. - */ - IFACEMETHOD_(bool, IsZoneEmpty)(ZoneIndex zoneIndex) const = 0; - /** - * Returns all zones spanned by the minimum bounding rectangle containing the two given zone index sets. - * - * @param initialZones The indices of the first chosen zone (the anchor). - * @param finalZones The indices of the last chosen zone (the current window position). - * - * @returns A vector indicating describing the chosen zone index set. - */ - IFACEMETHOD_(ZoneIndexSet, GetCombinedZoneRange)(const ZoneIndexSet& initialZones, const ZoneIndexSet& finalZones) const = 0; -}; - -struct ZoneSetConfig -{ - ZoneSetConfig( - GUID id, - FancyZonesDataTypes::ZoneSetLayoutType layoutType, - HMONITOR monitor, - int sensitivityRadius, - OverlappingZonesAlgorithm selectionAlgorithm = {}) noexcept : - Id(id), - LayoutType(layoutType), - Monitor(monitor), - SensitivityRadius(sensitivityRadius), - SelectionAlgorithm(selectionAlgorithm) - { - } - - GUID Id{}; - FancyZonesDataTypes::ZoneSetLayoutType LayoutType{}; - HMONITOR Monitor{}; - int SensitivityRadius; - OverlappingZonesAlgorithm SelectionAlgorithm = OverlappingZonesAlgorithm::Smallest; -}; - -winrt::com_ptr MakeZoneSet(ZoneSetConfig const& config) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.cpp b/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.cpp index 812a1e4c44..b5908eade7 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.cpp @@ -303,19 +303,14 @@ void ZonesOverlay::DrawActiveZoneSet(const ZonesMap& zones, // First draw the inactive zones for (const auto& [zoneId, zone] : zones) { - if (!zone) - { - continue; - } - if (!isHighlighted[zoneId]) { DrawableRect drawableRect{ - .rect = ConvertRect(zone->GetZoneRect()), + .rect = ConvertRect(zone.GetZoneRect()), .borderColor = borderColor, .fillColor = inactiveColor, .textColor = numberColor, - .id = zone->Id(), + .id = zone.Id(), .showText = showZoneText }; @@ -326,19 +321,14 @@ void ZonesOverlay::DrawActiveZoneSet(const ZonesMap& zones, // Draw the active zones on top of the inactive zones for (const auto& [zoneId, zone] : zones) { - if (!zone) - { - continue; - } - if (isHighlighted[zoneId]) { DrawableRect drawableRect{ - .rect = ConvertRect(zone->GetZoneRect()), + .rect = ConvertRect(zone.GetZoneRect()), .borderColor = borderColor, .fillColor = highlightColor, .textColor = numberColor, - .id = zone->Id(), + .id = zone.Id(), .showText = showZoneText }; diff --git a/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.h b/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.h index 6589cdb52f..1c5eb03a9c 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.h +++ b/src/modules/fancyzones/FancyZonesLib/ZonesOverlay.h @@ -9,9 +9,9 @@ #include "util.h" #include "Zone.h" -#include "ZoneSet.h" #include "FancyZones.h" #include "Colors.h" +#include "LayoutConfigurator.h" class ZonesOverlay { diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 6dc427c882..5362780f49 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "trace.h" -#include "FancyZonesLib/ZoneSet.h" +#include "FancyZonesLib/Layout.h" +#include "FancyZonesLib/LayoutAssignedWindows.h" #include "FancyZonesLib/Settings.h" #include "FancyZonesData/AppZoneHistory.h" #include "FancyZonesLib/FancyZonesData/AppliedLayouts.h" @@ -89,17 +90,17 @@ struct ZoneSetInfo }; -ZoneSetInfo GetZoneSetInfo(_In_opt_ IZoneSet* set) noexcept +ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept { ZoneSetInfo info; - if (set) + if (layout && layoutWindows) { - auto zones = set->GetZones(); + auto zones = layout->Zones(); info.NumberOfZones = zones.size(); info.NumberOfWindows = 0; for (int i = 0; i < static_cast(zones.size()); i++) { - if (!set->IsZoneEmpty(i)) + if (!layoutWindows->IsZoneEmpty(i)) { info.NumberOfWindows++; } @@ -108,11 +109,6 @@ ZoneSetInfo GetZoneSetInfo(_In_opt_ IZoneSet* set) noexcept return info; } -ZoneSetInfo GetZoneSetInfo(_In_opt_ winrt::com_ptr set) noexcept -{ - return GetZoneSetInfo(set.get()); -} - void Trace::RegisterProvider() noexcept { TraceLoggingRegister(g_hProvider); @@ -263,28 +259,28 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } -void Trace::FancyZones::SnapNewWindowIntoZone(IZoneSet* activeSet) noexcept +void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept { - auto const zoneInfo = GetZoneSetInfo(activeSet); + auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( g_hProvider, EventSnapNewWindowIntoZone, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(reinterpret_cast(activeSet), ActiveSetKey), + TraceLoggingValue(reinterpret_cast(activeLayout), ActiveSetKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::FancyZones::KeyboardSnapWindowToZone(IZoneSet* activeSet) noexcept +void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept { - auto const zoneInfo = GetZoneSetInfo(activeSet); + auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( g_hProvider, EventKeyboardSnapWindowToZone, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(reinterpret_cast(activeSet), ActiveSetKey), + TraceLoggingValue(reinterpret_cast(activeLayout), ActiveSetKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } @@ -361,41 +357,41 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept TraceLoggingValue(wParam, KeyboardValueKey)); } -void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ winrt::com_ptr activeSet) noexcept +void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept { - auto const zoneInfo = GetZoneSetInfo(activeSet); + auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( g_hProvider, EventMoveOrResizeStartedKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(reinterpret_cast(activeSet.get()), ActiveSetKey), + TraceLoggingValue(reinterpret_cast(activeLayout), ActiveSetKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept +void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept { - auto const zoneInfo = GetZoneSetInfo(activeSet); + auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( g_hProvider, EventMoveOrResizeEndedKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(reinterpret_cast(activeSet.get()), ActiveSetKey), + TraceLoggingValue(reinterpret_cast(activeLayout), ActiveSetKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ winrt::com_ptr activeSet, InputMode mode) noexcept +void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept { - auto const zoneInfo = GetZoneSetInfo(activeSet); + auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( g_hProvider, EventCycleActiveZoneSetKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(reinterpret_cast(activeSet.get()), ActiveSetKey), + TraceLoggingValue(reinterpret_cast(activeLayout), ActiveSetKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey), TraceLoggingValue(static_cast(mode), InputModeKey)); diff --git a/src/modules/fancyzones/FancyZonesLib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h index 9435bdcc9f..2e67df6f26 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.h +++ b/src/modules/fancyzones/FancyZonesLib/trace.h @@ -1,7 +1,8 @@ #pragma once struct Settings; -interface IZoneSet; +class Layout; +class LayoutAssignedWindows; class Trace { @@ -18,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(IZoneSet* activeSet) noexcept; - static void KeyboardSnapWindowToZone(IZoneSet* activeSet) noexcept; + static void SnapNewWindowIntoZone(Layout* activeaLayout, LayoutAssignedWindows* layoutWindows) noexcept; + static void KeyboardSnapWindowToZone(Layout* activeaLayout, LayoutAssignedWindows* layoutWindows) noexcept; }; static void SettingsTelemetry(const Settings& settings) noexcept; @@ -35,8 +36,8 @@ public: }; static void KeyUp(WPARAM wparam) noexcept; - static void MoveOrResizeStarted(_In_opt_ winrt::com_ptr activeSet) noexcept; - static void MoveOrResizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept; - static void CycleActiveZoneSet(_In_opt_ winrt::com_ptr activeSet, InputMode mode) 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; }; }; diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp index f9e9ba8510..87f5da5a3c 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp @@ -418,7 +418,7 @@ namespace FancyZonesUnitTests }; // test - Layout expectedLayout { + LayoutData expectedLayout { .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, .showSpacing = true, @@ -470,7 +470,7 @@ namespace FancyZonesUnitTests AppliedLayouts::instance().LoadData(); // test - Layout expectedLayout{ + LayoutData expectedLayout{ .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, .showSpacing = true, @@ -530,7 +530,7 @@ namespace FancyZonesUnitTests .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() }; - AppliedLayouts::instance().ApplyLayout(id, Layout{}); + AppliedLayouts::instance().ApplyLayout(id, LayoutData{}); // test Assert::IsTrue(AppliedLayouts::instance().IsLayoutApplied(id)); @@ -543,7 +543,7 @@ namespace FancyZonesUnitTests .monitorId = { .deviceId = { .id = L"device-1", .instanceId = L"instance-id-1" }, .serialNumber = L"serial-number-1" }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() }; - AppliedLayouts::instance().ApplyLayout(id1, Layout{}); + AppliedLayouts::instance().ApplyLayout(id1, LayoutData{}); // test FancyZonesDataTypes::WorkAreaId id2{ diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/DefaultLayoutsTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/DefaultLayoutsTests.Spec.cpp index df0ca99f6e..feda8cb003 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/DefaultLayoutsTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/DefaultLayoutsTests.Spec.cpp @@ -59,13 +59,13 @@ namespace FancyZonesUnitTests // test DefaultLayouts::instance().LoadData(); - Layout horizontal{ + LayoutData horizontal{ .uuid = FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value(), .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom }; Assert::IsTrue(horizontal == DefaultLayouts::instance().GetDefaultLayout(MonitorConfiguraionType::Horizontal)); - Layout vertical{ + LayoutData vertical{ .uuid = GUID_NULL, .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, .showSpacing = true, @@ -87,7 +87,7 @@ namespace FancyZonesUnitTests // test DefaultLayouts::instance().LoadData(); - Layout priorityGrid{ + LayoutData priorityGrid{ .uuid = GUID_NULL, .type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid, .showSpacing = DefaultValues::ShowSpacing, @@ -105,7 +105,7 @@ namespace FancyZonesUnitTests // test DefaultLayouts::instance().LoadData(); - Layout priorityGrid{ + LayoutData priorityGrid{ .uuid = GUID_NULL, .type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid, .showSpacing = DefaultValues::ShowSpacing, diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp new file mode 100644 index 0000000000..616d50b928 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp @@ -0,0 +1,487 @@ +#include "pch.h" + +#include + +#include +#include +#include +#include +#include + +#include "Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace FancyZonesDataTypes; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (LayoutUnitTests) + { + const LayoutData m_data + { + .uuid = FancyZonesUtils::GuidFromString(L"{F762BAD6-DAA1-4997-9497-E11DFEB72F21}").value(), + .type = ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 17, + .zoneCount = 4, + .sensitivityRadius = 33 + }; + std::unique_ptr m_layout{}; + + TEST_METHOD_INITIALIZE(Init) + { + m_layout = std::make_unique(m_data); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove_all(CustomLayouts::CustomLayoutsFileName()); + } + + void compareZones(const Zone& expected, const Zone& actual) + { + Assert::AreEqual(expected.Id(), actual.Id()); + Assert::AreEqual(expected.GetZoneRect().left, actual.GetZoneRect().left); + Assert::AreEqual(expected.GetZoneRect().right, actual.GetZoneRect().right); + Assert::AreEqual(expected.GetZoneRect().top, actual.GetZoneRect().top); + Assert::AreEqual(expected.GetZoneRect().bottom, actual.GetZoneRect().bottom); + } + + void saveCustomLayout(const std::vector& zones) + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + json::JsonObject canvasLayoutJson{}; + canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(m_data.uuid).value())); + canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::NameID, json::value(L"Custom canvas layout")); + canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::TypeID, json::value(NonLocalizable::CustomLayoutsIds::CanvasID)); + + json::JsonObject info{}; + info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RefWidthID, json::value(1920)); + info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RefHeightID, json::value(1080)); + + json::JsonArray zonesArray{}; + for (const auto& zoneRect : zones) + { + json::JsonObject zone{}; + zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::XID, json::value(zoneRect.left)); + zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::YID, json::value(zoneRect.top)); + zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::WidthID, json::value(zoneRect.right - zoneRect.left)); + zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::HeightID, json::value(zoneRect.bottom - zoneRect.top)); + zonesArray.Append(zone); + } + + info.SetNamedValue(NonLocalizable::CustomLayoutsIds::ZonesID, zonesArray); + canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::InfoID, info); + layoutsArray.Append(canvasLayoutJson); + root.SetNamedValue(NonLocalizable::CustomLayoutsIds::CustomLayoutsArrayID, layoutsArray); + json::to_file(CustomLayouts::CustomLayoutsFileName(), root); + + CustomLayouts::instance().LoadData(); + } + + public: + TEST_METHOD (TestCreateLayout) + { + CustomAssert::AreEqual(m_layout->Id(), m_data.uuid); + CustomAssert::AreEqual(m_layout->Type(), m_data.type); + } + + TEST_METHOD (EmptyZones) + { + auto zones = m_layout->Zones(); + Assert::AreEqual((size_t)0, zones.size()); + } + + TEST_METHOD (ZoneFromPointEmpty) + { + auto actual = m_layout->ZonesFromPoint(POINT{ 0, 0 }); + Assert::IsTrue(actual.size() == 0); + } + + TEST_METHOD (ZoneFromPointInner) + { + LayoutData data = m_data; + data.spacing = 0; + auto layout = std::make_unique(data); + layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); + + auto actual = layout->ZonesFromPoint(POINT{ 1, 1 }); + Assert::IsTrue(actual.size() == 1); + } + + TEST_METHOD (ZoneFromPointBorder) + { + LayoutData data = m_data; + data.spacing = 0; + auto layout = std::make_unique(data); + layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); + + Assert::IsTrue(layout->ZonesFromPoint(POINT{ 0, 0 }).size() == 1); + Assert::IsTrue(layout->ZonesFromPoint(POINT{ 1920, 1080 }).size() == 0); + } + + TEST_METHOD (ZoneFromPointOuter) + { + m_layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); + + auto actual = m_layout->ZonesFromPoint(POINT{ 1921, 1080 }); + Assert::IsTrue(actual.size() == 0); + } + + TEST_METHOD (ZoneFromPointOverlapping) + { + // prepare layout with overlapping zones + saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 10, 10, 90, 90 }, RECT{ 10, 10, 150, 150 }, RECT{ 10, 10, 50, 50 } }); + + LayoutData data = m_data; + data.type = FancyZonesDataTypes::ZoneSetLayoutType::Custom; + 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 layout = std::make_unique(data); + layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); + + // zone4 is expected because it's the smallest one, and it's considered to be inside + // since Multizones support + auto zones = layout->ZonesFromPoint(POINT{ 50, 50 }); + Assert::IsTrue(zones.size() == 1); + + Zone expected({ 10, 10, 50, 50 }, 3); + const auto& actual = layout->Zones().at(zones.at(0)); + compareZones(expected, actual); + } + + TEST_METHOD (ZoneFromPointMultizone) + { + // prepare layout with overlapping zones + saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 100, 0, 200, 100 }, RECT{ 0, 100, 100, 200 }, RECT{ 100, 100, 200, 200 } }); + + LayoutData data = m_data; + data.type = FancyZonesDataTypes::ZoneSetLayoutType::Custom; + 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 layout = std::make_unique(data); + layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); + + auto actual = layout->ZonesFromPoint(POINT{ 50, 100 }); + Assert::IsTrue(actual.size() == 2); + + Zone zone1({ 0, 0, 100, 100 }, 0); + compareZones(zone1, layout->Zones().at(actual[0])); + + Zone zone3({ 0, 100, 100, 200 }, 2); + compareZones(zone3, layout->Zones().at(actual[1])); + } + }; + + TEST_CLASS (LayoutInitUnitTests) + { + const LayoutData m_data{ + .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), + .type = ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 17, + .zoneCount = 4, + .sensitivityRadius = 33 + }; + std::unique_ptr m_layout{}; + + HMONITOR m_monitor{}; + const std::array m_workAreaRects{ + RECT{ .left = 0, .top = 0, .right = 1024, .bottom = 768 }, + RECT{ .left = 0, .top = 0, .right = 1280, .bottom = 720 }, + RECT{ .left = 0, .top = 0, .right = 1280, .bottom = 800 }, + RECT{ .left = 0, .top = 0, .right = 1280, .bottom = 1024 }, + RECT{ .left = 0, .top = 0, .right = 1366, .bottom = 768 }, + RECT{ .left = 0, .top = 0, .right = 1440, .bottom = 900 }, + RECT{ .left = 0, .top = 0, .right = 1536, .bottom = 864 }, + RECT{ .left = 0, .top = 0, .right = 1600, .bottom = 900 }, + RECT{ .left = 0, .top = 0, .right = 1920, .bottom = 1080 } + }; + + void checkZones(const std::unique_ptr& layout, ZoneSetLayoutType type, size_t expectedCount, RECT rect) + { + const auto& zones = layout->Zones(); + Assert::AreEqual(expectedCount, zones.size()); + + int zoneId = 0; + for (const auto& zone : zones) + { + const auto& zoneRect = zone.second.GetZoneRect(); + Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); + Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); + + Assert::IsTrue(zoneRect.left < zoneRect.right, L"rect.left >= rect.right"); + Assert::IsTrue(zoneRect.top < zoneRect.bottom, L"rect.top >= rect.bottom"); + + if (type != ZoneSetLayoutType::Focus) + { + Assert::IsTrue(zoneRect.right <= rect.right, L"right border is bigger than monitor work space"); + Assert::IsTrue(zoneRect.bottom <= rect.bottom, L"bottom border is bigger than monitor work space"); + } + + zoneId++; + } + } + + TEST_METHOD_INITIALIZE(Init) + { + m_layout = std::make_unique(m_data); + } + + public: + TEST_METHOD (ValidValues) + { + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.spacing = 10; + data.zoneCount = zoneCount; + + for (const auto& rect : m_workAreaRects) + { + auto layout = std::make_unique(data); + auto result = layout->Init(rect, Mocks::Monitor()); + Assert::IsTrue(result); + checkZones(layout, static_cast(type), zoneCount, rect); + } + } + } + + TEST_METHOD (InvalidMonitorInfo) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.spacing = 10; + data.zoneCount = 10; + + auto layout = std::make_unique(data); + + auto result = layout->Init(RECT{0,0,0,0}, Mocks::Monitor()); + Assert::IsFalse(result); + } + } + + TEST_METHOD (ZeroSpacing) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.spacing = 0; + data.zoneCount = 10; + + for (const auto& rect : m_workAreaRects) + { + auto layout = std::make_unique(data); + auto result = layout->Init(rect, Mocks::Monitor()); + Assert::IsTrue(result); + checkZones(layout, static_cast(type), data.zoneCount, rect); + } + } + } + + TEST_METHOD (LargeNegativeSpacing) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.zoneCount = 10; + data.spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1; + + auto layout = std::make_unique(data); + + for (const auto& rect : m_workAreaRects) + { + auto result = layout->Init(rect, Mocks::Monitor()); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (HorizontallyBigSpacing) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.zoneCount = 10; + + for (const auto& rect : m_workAreaRects) + { + data.spacing = rect.right; + auto layout = std::make_unique(data); + + auto result = layout->Init(rect, Mocks::Monitor()); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depend on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (VerticallyBigSpacing) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.zoneCount = 10; + + for (const auto& rect : m_workAreaRects) + { + data.spacing = rect.bottom; + auto layout = std::make_unique(data); + + auto result = layout->Init(rect, Mocks::Monitor()); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depend on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (ZeroZoneCount) + { + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.zoneCount = 0; + auto layout = std::make_unique(data); + + for (const auto& rect : m_workAreaRects) + { + auto result = layout->Init(rect, Mocks::Monitor()); + Assert::IsFalse(result); + } + } + } + + TEST_METHOD (BigZoneCount) + { + const int zoneCount = 128; //editor limit + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + LayoutData data = m_data; + data.type = static_cast(type); + data.zoneCount = zoneCount; + data.spacing = 0; + + for (const auto& rect : m_workAreaRects) + { + auto layout = std::make_unique(data); + auto result = layout->Init(rect, Mocks::Monitor()); + Assert::IsTrue(result); + checkZones(layout, static_cast(type), zoneCount, rect); + } + } + } + }; + + TEST_CLASS (ZoneIndexSetUnitTests) + { + TEST_METHOD (BitmaskFromIndexSetTest) + { + // prepare + ZoneIndexSet set{ 0, 64 }; + + // test + ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); + Assert::AreEqual(static_cast(1), bitmask.part1); + Assert::AreEqual(static_cast(1), bitmask.part2); + } + + TEST_METHOD (BitmaskToIndexSet) + { + // prepare + ZoneIndexSetBitmask bitmask{ + .part1 = 1, + .part2 = 1, + }; + + // test + ZoneIndexSet set = bitmask.ToIndexSet(); + Assert::AreEqual(static_cast(2), set.size()); + Assert::AreEqual(static_cast(0), set[0]); + Assert::AreEqual(static_cast(64), set[1]); + } + + TEST_METHOD (BitmaskConvertTest) + { + // prepare + ZoneIndexSet set{ 53, 54, 55, 65, 66, 67 }; + + ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); + + // test + ZoneIndexSet actual = bitmask.ToIndexSet(); + Assert::AreEqual(set.size(), actual.size()); + for (int i = 0; i < set.size(); i++) + { + Assert::AreEqual(set[i], actual[i]); + } + } + + TEST_METHOD (BitmaskConvert2Test) + { + // prepare + ZoneIndexSet set; + for (int i = 0; i < 128; i++) + { + set.push_back(i); + } + + ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); + + // test + ZoneIndexSet actual = bitmask.ToIndexSet(); + + Assert::AreEqual(set.size(), actual.size()); + for (int i = 0; i < set.size(); i++) + { + Assert::AreEqual(set[i], actual[i]); + } + } + }; +} diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutAssignedWindows.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutAssignedWindows.Spec.cpp new file mode 100644 index 0000000000..eecfd7cc48 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/LayoutAssignedWindows.Spec.cpp @@ -0,0 +1,97 @@ +#include "pch.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace FancyZonesDataTypes; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (LayoutAssignedWindowsUnitTests) + { + TEST_METHOD (ZoneIndexFromWindowUnknown) + { + LayoutAssignedWindows layoutWindows{}; + + layoutWindows.Assign(Mocks::Window(), { 0 }); + + auto actual = layoutWindows.GetZoneIndexSetFromWindow(Mocks::Window()); + Assert::IsTrue(std::vector{} == actual); + } + + TEST_METHOD (ZoneIndexFromWindowNull) + { + LayoutAssignedWindows layoutWindows{}; + + layoutWindows.Assign(Mocks::Window(), { 0 }); + + auto actual = layoutWindows.GetZoneIndexSetFromWindow(nullptr); + Assert::IsTrue(std::vector{} == actual); + } + + TEST_METHOD (Assign) + { + HWND window = Mocks::Window(); + + LayoutAssignedWindows layoutWindows{}; + layoutWindows.Assign(window, { 1, 2, 3 }); + + Assert::IsTrue(std::vector{ 1, 2, 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (AssignEmpty) + { + HWND window = Mocks::Window(); + + LayoutAssignedWindows layoutWindows{}; + layoutWindows.Assign(window, {}); + + Assert::IsTrue(std::vector{} == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (AssignSeveralTimesSameWindow) + { + LayoutAssignedWindows layoutWindows{}; + HWND window = Mocks::Window(); + + layoutWindows.Assign(window, { 0 }); + Assert::IsTrue(std::vector{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + + layoutWindows.Assign(window, { 1 }); + Assert::IsTrue(std::vector{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + + layoutWindows.Assign(window, { 2 }); + Assert::IsTrue(std::vector{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (DismissWindow) + { + LayoutAssignedWindows layoutWindows{}; + HWND window = Mocks::Window(); + + layoutWindows.Assign(window, { 0 }); + + layoutWindows.Dismiss(window); + Assert::IsTrue(std::vector{} == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Empty) + { + LayoutAssignedWindows layoutWindows{}; + HWND window = Mocks::Window(); + + layoutWindows.Assign(window, { 0 }); + Assert::IsFalse(layoutWindows.IsZoneEmpty(0)); + Assert::IsTrue(layoutWindows.IsZoneEmpty(1)); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index 2086cd7620..e11bbb3278 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -47,8 +47,10 @@ + + Create @@ -57,7 +59,6 @@ - diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters index 1572c4c07a..799ea74a4a 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters @@ -18,9 +18,6 @@ Source Files - - Source Files - Source Files @@ -57,6 +54,12 @@ Source Files + + Source Files + + + Source Files + Source Files diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp index a7fb0524c9..edfeaf80f4 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Util.h" #include @@ -53,10 +54,11 @@ namespace FancyZonesUnitTests Assert::IsFalse(workArea == nullptr); Assert::IsTrue(m_uniqueId == workArea->UniqueId()); - auto* zoneSet{ workArea->ZoneSet() }; - Assert::IsNotNull(zoneSet); - Assert::AreEqual(static_cast(defaultLayout.type), static_cast(zoneSet->LayoutType())); - Assert::AreEqual(static_cast(defaultLayout.zoneCount), zoneSet->GetZones().size()); + 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())); } TEST_METHOD (CreateCombinedWorkArea) @@ -67,10 +69,11 @@ namespace FancyZonesUnitTests Assert::IsFalse(workArea == nullptr); Assert::IsTrue(m_uniqueId == workArea->UniqueId()); - auto* zoneSet{ workArea->ZoneSet() }; - Assert::IsNotNull(zoneSet); - Assert::AreEqual(static_cast(defaultLayout.type), static_cast(zoneSet->LayoutType())); - Assert::AreEqual(static_cast(defaultLayout.zoneCount), zoneSet->GetZones().size()); + 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())); } TEST_METHOD (CreateWorkAreaClonedFromParent) @@ -83,7 +86,7 @@ namespace FancyZonesUnitTests parentUniqueId.monitorId.serialNumber = L"serial-number"; parentUniqueId.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value(); - Layout layout{ + LayoutData layout{ .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(), .type = ZoneSetLayoutType::Rows, .showSpacing = true, @@ -96,10 +99,11 @@ namespace FancyZonesUnitTests AppliedLayouts::instance().ApplyLayout(parentUniqueId, layout); auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, parentUniqueId); - Assert::IsNotNull(actualWorkArea->ZoneSet()); + Assert::IsNotNull(actualWorkArea->GetLayout().get()); + Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get()); Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_uniqueId)); - auto actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_uniqueId); + const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_uniqueId); Assert::AreEqual(static_cast(layout.type), static_cast(actualLayout.type)); Assert::AreEqual(FancyZonesUtils::GuidToString(layout.uuid).value(), FancyZonesUtils::GuidToString(actualLayout.uuid).value()); @@ -131,10 +135,11 @@ namespace FancyZonesUnitTests Assert::IsFalse(workArea == nullptr); Assert::IsTrue(m_uniqueId == workArea->UniqueId()); - auto* zoneSet{ workArea->ZoneSet() }; - Assert::IsNotNull(zoneSet); - Assert::AreEqual(static_cast(FancyZonesDataTypes::ZoneSetLayoutType::Custom), static_cast(zoneSet->LayoutType())); - Assert::IsTrue(FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value() == zoneSet->Id()); + Assert::IsNotNull(workArea->GetLayout().get()); + + const auto& actualLayout = workArea->GetLayout(); + Assert::AreEqual(static_cast(FancyZonesDataTypes::ZoneSetLayoutType::Custom), static_cast(actualLayout->Type())); + Assert::IsTrue(FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value() == actualLayout->Id()); } TEST_METHOD (CreateWorkAreaWithTemplateDefault) @@ -163,11 +168,12 @@ namespace FancyZonesUnitTests Assert::IsFalse(workArea == nullptr); Assert::IsTrue(m_uniqueId == workArea->UniqueId()); - auto* zoneSet{ workArea->ZoneSet() }; - Assert::IsNotNull(zoneSet); - Assert::AreEqual(static_cast(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast(zoneSet->LayoutType())); - Assert::AreEqual(static_cast(4), zoneSet->GetZones().size()); - Assert::IsTrue(GUID_NULL == zoneSet->Id()); + Assert::IsNotNull(workArea->GetLayout().get()); + + const auto& actualLayout = workArea->GetLayout(); + Assert::AreEqual(static_cast(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast(actualLayout->Type())); + Assert::AreEqual(static_cast(4), actualLayout->Zones().size()); + Assert::IsTrue(GUID_NULL == actualLayout->Id()); } }; @@ -255,33 +261,17 @@ namespace FancyZonesUnitTests const auto window = Mocks::Window(); workArea->MoveSizeEnter(window); + workArea->MoveSizeUpdate({ 20, 20 }, true, true); const auto expected = S_OK; - const auto actual = workArea->MoveSizeEnd(window, POINT{ 0, 0 }); + const auto actual = workArea->MoveSizeEnd(window); Assert::AreEqual(expected, actual); - const auto zoneSet = workArea->ZoneSet(); - zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window); + const auto& layoutWindows = workArea->GetLayoutWindows(); + const auto actualZoneIndexSet = layoutWindows->GetZoneIndexSetFromWindow(window); Assert::IsFalse(std::vector{} == actualZoneIndexSet); } - TEST_METHOD (MoveSizeEndWindowNotAdded) - { - auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - - const auto window = Mocks::Window(); - workArea->MoveSizeEnter(window); - - const auto expected = S_OK; - const auto actual = workArea->MoveSizeEnd(window, POINT{ -100, -100 }); - Assert::AreEqual(expected, actual); - - const auto zoneSet = workArea->ZoneSet(); - const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window); - Assert::IsTrue(std::vector{} == actualZoneIndexSet); - } - TEST_METHOD (MoveSizeEndDifferentWindows) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); @@ -290,7 +280,7 @@ namespace FancyZonesUnitTests workArea->MoveSizeEnter(window); const auto expected = E_INVALIDARG; - const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); + const auto actual = workArea->MoveSizeEnd(Mocks::Window()); Assert::AreEqual(expected, actual); } @@ -300,74 +290,14 @@ namespace FancyZonesUnitTests auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); const auto expected = E_INVALIDARG; - const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); + const auto actual = workArea->MoveSizeEnd(Mocks::Window()); Assert::AreEqual(expected, actual); } - TEST_METHOD (MoveSizeEndInvalidPoint) - { - auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - - const auto window = Mocks::Window(); - workArea->MoveSizeEnter(window); - - const auto expected = S_OK; - const auto actual = workArea->MoveSizeEnd(window, POINT{ -1, -1 }); - Assert::AreEqual(expected, actual); - - const auto zoneSet = workArea->ZoneSet(); - zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - const auto actualZoneIndex = zoneSet->GetZoneIndexSetFromWindow(window); - Assert::IsFalse(std::vector{} == actualZoneIndex); // with invalid point zone remains the same - } - - TEST_METHOD (MoveWindowIntoZoneByIndex) - { - auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - - workArea->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); - - const auto actual = workArea->ZoneSet(); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionAndIndex) - { - auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - const auto& appHistoryArray = actualAppZoneHistory.begin()->second; - Assert::AreEqual((size_t)1, appHistoryArray.size()); - Assert::IsTrue(std::vector{ 0 } == appHistoryArray[0].zoneIndexSet); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionManyTimes) - { - auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - const auto& appHistoryArray = actualAppZoneHistory.begin()->second; - Assert::AreEqual((size_t)1, appHistoryArray.size()); - Assert::IsTrue(std::vector{ 2 } == appHistoryArray[0].zoneIndexSet); - } - TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); workArea->SaveWindowProcessToZoneIndex(nullptr); @@ -378,10 +308,9 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); auto window = Mocks::WindowCreate(m_hInst); - workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); + workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); workArea->SaveWindowProcessToZoneIndex(window); @@ -392,22 +321,21 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - + const auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); const auto deviceId = workArea->UniqueId(); - const auto zoneSetId = workArea->ZoneSet()->Id(); + const auto& layoutId = workArea->GetLayout()->Id(); // fill app zone history map - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(zoneSetId), { 0 })); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(layoutId), { 0 })); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size()); const auto& appHistoryArray1 = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath); Assert::AreEqual((size_t)1, appHistoryArray1.size()); Assert::IsTrue(std::vector{ 0 } == appHistoryArray1[0].zoneIndexSet); // add zone without window - workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); + workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); workArea->SaveWindowProcessToZoneIndex(window); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size()); @@ -419,18 +347,16 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - + auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); const auto deviceId = workArea->UniqueId(); - const auto zoneSetId = workArea->ZoneSet()->Id(); + const auto& layoutId = workArea->GetLayout()->Id(); - workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); workArea->MoveWindowIntoZoneByIndex(window, 0); //fill app zone history map - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(zoneSetId), { 2 })); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(layoutId), { 2 })); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size()); const auto& appHistoryArray = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath); Assert::AreEqual((size_t)1, appHistoryArray.size()); @@ -440,7 +366,7 @@ namespace FancyZonesUnitTests const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - const auto& expected = workArea->ZoneSet()->GetZoneIndexSetFromWindow(window); + const auto& expected = workArea->GetLayoutWindows()->GetZoneIndexSetFromWindow(window); const auto& actual = appHistoryArray[0].zoneIndexSet; Assert::IsTrue(expected == actual); } @@ -448,8 +374,7 @@ namespace FancyZonesUnitTests TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); - Assert::IsNotNull(workArea->ZoneSet()); - + auto window = Mocks::WindowCreate(m_hInst); int originalWidth = 450; @@ -458,7 +383,7 @@ namespace FancyZonesUnitTests SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW); SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX); - workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); + workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); RECT inZoneRect; @@ -467,4 +392,425 @@ namespace FancyZonesUnitTests Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top); } }; + + TEST_CLASS (WorkAreaMoveWindowUnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const FancyZonesDataTypes::WorkAreaId m_uniqueId{ + .monitorId = { + .monitor = Mocks::Monitor(), + .deviceId = { + .id = L"DELA026", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number" + }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + + FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty + + HINSTANCE m_hInst{}; + HMONITOR m_monitor{}; + + 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_uniqueId.monitorId.deviceId.id)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_uniqueId.monitorId.deviceId.instanceId)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_uniqueId.monitorId.serialNumber)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_uniqueId.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_uniqueId.monitorId.deviceId.id)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_uniqueId.monitorId.deviceId.instanceId)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_uniqueId.monitorId.serialNumber)); + workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_uniqueId.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) + { + AppZoneHistory::instance().LoadData(); + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + } + + TEST_METHOD (EmptyZonesMoveLeftByIndex) + { + // prepare + PrepareEmptyLayout(); + auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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{ (ZoneIndex)workArea->GetLayout()->Zones().size() - 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveAppliedWindowByIndexNoCycle) + { + // prepare + PrepareGridLayout(); + auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + 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->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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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 = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId); + const auto window = Mocks::WindowCreate(m_hInst); + workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 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)); + } + }; } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp index c1ff9d9810..f0ee4ffd01 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp @@ -22,25 +22,38 @@ namespace FancyZonesUnitTests public: TEST_METHOD(TestCreateZone) { - winrt::com_ptr zone = MakeZone(m_zoneRect, 1); - Assert::IsNotNull(&zone); - CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect()); + Zone zone(m_zoneRect, 1); + Assert::IsTrue(zone.IsValid()); + CustomAssert::AreEqual(m_zoneRect, zone.GetZoneRect()); } TEST_METHOD(TestCreateZoneZeroRect) { RECT zoneRect{ 0, 0, 0, 0 }; - winrt::com_ptr zone = MakeZone(zoneRect, 1); - Assert::IsNotNull(&zone); - CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); + Zone zone(zoneRect, 1); + Assert::IsTrue(zone.IsValid()); + CustomAssert::AreEqual(zoneRect, zone.GetZoneRect()); } TEST_METHOD(GetSetId) { constexpr ZoneIndex zoneId = 123; - winrt::com_ptr zone = MakeZone(m_zoneRect, zoneId); + Zone zone(m_zoneRect, zoneId); - Assert::AreEqual(zone->Id(), zoneId); + Assert::IsTrue(zone.IsValid()); + Assert::AreEqual(zone.Id(), zoneId); + } + + TEST_METHOD(InvalidId) + { + Zone zone(m_zoneRect, -1); + Assert::IsFalse(zone.IsValid()); + } + + TEST_METHOD (InvalidRect) + { + Zone zone({ 100, 100, 99, 101 }, 1); + Assert::IsFalse(zone.IsValid()); } }; } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp deleted file mode 100644 index fc0e391198..0000000000 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp +++ /dev/null @@ -1,851 +0,0 @@ -#include "pch.h" - -#include - -#include -#include -#include "FancyZonesLib\ZoneIndexSetBitmask.h" -#include "FancyZonesLib\ZoneSet.h" -#include - -#include "Util.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; -using namespace FancyZonesDataTypes; - -namespace FancyZonesUnitTests -{ - TEST_CLASS (ZoneSetUnitTests) - { - GUID m_id; - const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Grid; - - winrt::com_ptr m_set; - - TEST_METHOD_INITIALIZE(Init) - { - auto hres = CoCreateGuid(&m_id); - Assert::AreEqual(S_OK, hres); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - m_set = MakeZoneSet(m_config); - } - - TEST_METHOD_CLEANUP(CleanUp) - { - std::filesystem::remove_all(CustomLayouts::CustomLayoutsFileName()); - } - - void compareZones(const winrt::com_ptr& expected, const winrt::com_ptr& actual) - { - Assert::AreEqual(expected->Id(), actual->Id()); - Assert::AreEqual(expected->GetZoneRect().left, actual->GetZoneRect().left); - Assert::AreEqual(expected->GetZoneRect().right, actual->GetZoneRect().right); - Assert::AreEqual(expected->GetZoneRect().top, actual->GetZoneRect().top); - Assert::AreEqual(expected->GetZoneRect().bottom, actual->GetZoneRect().bottom); - } - - void saveCustomLayout(const std::vector& zones) - { - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - - json::JsonObject canvasLayoutJson{}; - canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(m_id).value())); - canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::NameID, json::value(L"Custom canvas layout")); - canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::TypeID, json::value(NonLocalizable::CustomLayoutsIds::CanvasID)); - - json::JsonObject info{}; - info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RefWidthID, json::value(1920)); - info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RefHeightID, json::value(1080)); - - json::JsonArray zonesArray{}; - for (const auto& zoneRect : zones) - { - json::JsonObject zone{}; - zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::XID, json::value(zoneRect.left)); - zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::YID, json::value(zoneRect.top)); - zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::WidthID, json::value(zoneRect.right - zoneRect.left)); - zone.SetNamedValue(NonLocalizable::CustomLayoutsIds::HeightID, json::value(zoneRect.bottom - zoneRect.top)); - zonesArray.Append(zone); - } - - info.SetNamedValue(NonLocalizable::CustomLayoutsIds::ZonesID, zonesArray); - canvasLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::InfoID, info); - layoutsArray.Append(canvasLayoutJson); - root.SetNamedValue(NonLocalizable::CustomLayoutsIds::CustomLayoutsArrayID, layoutsArray); - json::to_file(CustomLayouts::CustomLayoutsFileName(), root); - - CustomLayouts::instance().LoadData(); - } - - public: - TEST_METHOD (TestCreateZoneSet) - { - Assert::IsNotNull(&m_set); - CustomAssert::AreEqual(m_set->Id(), m_id); - CustomAssert::AreEqual(m_set->LayoutType(), m_layoutType); - } - - TEST_METHOD (TestCreateZoneSetGuidEmpty) - { - GUID zoneSetId{}; - ZoneSetConfig config(zoneSetId, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius); - winrt::com_ptr set = MakeZoneSet(config); - - Assert::IsNotNull(&set); - CustomAssert::AreEqual(set->Id(), zoneSetId); - CustomAssert::AreEqual(set->LayoutType(), m_layoutType); - } - - TEST_METHOD (EmptyZones) - { - auto zones = m_set->GetZones(); - Assert::AreEqual((size_t)0, zones.size()); - } - - TEST_METHOD (MakeZoneFromZeroRect) - { - winrt::com_ptr zone = MakeZone({ 0, 0, 0, 0 }, 1); - Assert::IsNotNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectWidth) - { - winrt::com_ptr zone = MakeZone({ 100, 100, 99, 101 }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectHeight) - { - winrt::com_ptr zone = MakeZone({ 100, 100, 101, 99 }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectCoords) - { - const int invalid = ZoneConstants::MAX_NEGATIVE_SPACING - 1; - winrt::com_ptr zone = MakeZone({ invalid, invalid, invalid, invalid }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (ZoneFromPointEmpty) - { - auto actual = m_set->ZonesFromPoint(POINT{ 0, 0 }); - Assert::IsTrue(actual.size() == 0); - } - - TEST_METHOD (ZoneFromPointInner) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - auto actual = m_set->ZonesFromPoint(POINT{ 1, 1 }); - Assert::IsTrue(actual.size() == 1); - } - - TEST_METHOD (ZoneFromPointBorder) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - Assert::IsTrue(m_set->ZonesFromPoint(POINT{ 0, 0 }).size() == 1); - Assert::IsTrue(m_set->ZonesFromPoint(POINT{ 1920, 1080 }).size() == 0); - } - - TEST_METHOD (ZoneFromPointOuter) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - auto actual = m_set->ZonesFromPoint(POINT{ 1921, 1080 }); - Assert::IsTrue(actual.size() == 0); - } - - TEST_METHOD (ZoneFromPointOverlapping) - { - // prepare layout with overlapping zones - saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 10, 10, 90, 90 }, RECT{ 10, 10, 150, 150 }, RECT{ 10, 10, 50, 50 } }); - - ZoneSetConfig config = ZoneSetConfig(m_id, FancyZonesDataTypes::ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - auto set = MakeZoneSet(config); - set->CalculateZones(RECT{0,0,1920,1080}, 4, 0); - - // zone4 is expected because it's the smallest one, and it's considered to be inside - // since Multizones support - auto zones = set->ZonesFromPoint(POINT{ 50, 50 }); - Assert::IsTrue(zones.size() == 1); - - auto expected = MakeZone({ 10, 10, 50, 50 }, 3); - auto actual = set->GetZones()[zones[0]]; - compareZones(expected, actual); - } - - TEST_METHOD (ZoneFromPointMultizone) - { - // prepare layout with overlapping zones - saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 100, 0, 200, 100 }, RECT{ 0, 100, 100, 200 }, RECT{ 100, 100, 200, 200 } }); - - ZoneSetConfig config = ZoneSetConfig(m_id, FancyZonesDataTypes::ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - auto set = MakeZoneSet(config); - set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 4, 0); - - auto actual = set->ZonesFromPoint(POINT{ 50, 100 }); - Assert::IsTrue(actual.size() == 2); - - auto zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - compareZones(zone1, set->GetZones()[actual[0]]); - - auto zone3 = MakeZone({ 0, 100, 100, 200 }, 2); - compareZones(zone3, set->GetZones()[actual[1]]); - } - - TEST_METHOD (ZoneIndexFromWindowUnknown) - { - HWND window = Mocks::Window(); - HWND workArea = Mocks::Window(); - m_set->CalculateZones(RECT{0,0,1920, 1080}, 1, 0); - m_set->MoveWindowIntoZoneByIndexSet(window, workArea, { 0 }); - - auto actual = m_set->GetZoneIndexSetFromWindow(Mocks::Window()); - Assert::IsTrue(std::vector{} == actual); - } - - TEST_METHOD (ZoneIndexFromWindowNull) - { - HWND window = Mocks::Window(); - HWND workArea = Mocks::Window(); - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - m_set->MoveWindowIntoZoneByIndexSet(window, workArea, { 0 }); - - auto actual = m_set->GetZoneIndexSetFromWindow(nullptr); - Assert::IsTrue(std::vector{} == actual); - } - - TEST_METHOD (MoveWindowIntoZoneByIndex) - { - // prepare layout with overlapping zones - saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 0, 0, 100, 100 }, RECT{ 0, 0, 100, 100 } }); - - ZoneSetConfig config = ZoneSetConfig(m_id, FancyZonesDataTypes::ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - auto set = MakeZoneSet(config); - set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 0); - - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - Assert::IsTrue(std::vector{ 1 } == set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexWithNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexWithInvalidIndex) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); - Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameWindow) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 0); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameIndex) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 0); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointEmpty) - { - m_set->MoveWindowIntoZoneByPoint(Mocks::Window(), Mocks::Window(), POINT{ 0, 0 }); - } - - TEST_METHOD (MoveWindowIntoZoneByPointOuterPoint) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - auto window = Mocks::Window(); - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 1921, 1081 }); - - Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointInnerPoint) - { - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0); - - auto window = Mocks::Window(); - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointInnerPointOverlappingZones) - { - saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 10, 10, 90, 90 } }); - - ZoneSetConfig config = ZoneSetConfig(m_id, FancyZonesDataTypes::ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - auto set = MakeZoneSet(config); - set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 0); - - auto window = Mocks::Window(); - set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 1 } == set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindow) - { - saveCustomLayout({ RECT{ 0, 0, 100, 100 }, RECT{ 10, 10, 90, 90 } }); - - ZoneSetConfig config = ZoneSetConfig(m_id, FancyZonesDataTypes::ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius, OverlappingZonesAlgorithm::Smallest); - auto set = MakeZoneSet(config); - set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 0); - - const auto window = Mocks::Window(); - const auto workArea = Mocks::Window(); - - set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 1 } == set->GetZoneIndexSetFromWindow(window)); - } - }; - - // MoveWindowIntoZoneByDirectionAndIndex is complicated enough to warrant it's own test class - TEST_CLASS (ZoneSetsMoveWindowIntoZoneByDirectionUnitTests) - { - winrt::com_ptr m_set; - winrt::com_ptr m_zone1; - winrt::com_ptr m_zone2; - winrt::com_ptr m_zone3; - - TEST_METHOD_INITIALIZE(Initialize) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Grid, Mocks::Monitor(), DefaultValues::SensitivityRadius); - m_set = MakeZoneSet(config); - m_set->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 3, 10); - } - - TEST_METHOD (EmptyZonesLeft) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(config); - - set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_LEFT, true); - } - - TEST_METHOD (EmptyZonesRight) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(config); - - set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_RIGHT, true); - } - - TEST_METHOD (MoveRightNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightTwice) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftTwice) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightMoreThanZonesCount) - { - HWND window = Mocks::Window(); - for (int i = 0; i <= m_set->GetZones().size(); i++) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - } - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftMoreThanZonesCount) - { - HWND window = Mocks::Window(); - for (int i = 0; i <= m_set->GetZones().size(); i++) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - } - - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionRight) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightWithSameWindowAdded) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1 }); - - Assert::IsTrue(std::vector{ 0, 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightWithDifferentWindowsAdded) - { - HWND window1 = Mocks::Window(); - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), { 0 }); - m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), { 1 }); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionLeft) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftWithSameWindowAdded) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 1, 2 }); - - Assert::IsTrue(std::vector{ 1, 2 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftWithDifferentWindowsAdded) - { - HWND window1 = Mocks::Window(); - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 1); - m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), 2); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundRight) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundLeft) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveSecondWindowIntoSameZone) - { - HWND window1 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 0); - - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveRightMoreThanZoneCountReturnsFalse) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); - } - bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); - Assert::IsFalse(moreZonesInLayout); - } - - TEST_METHOD (MoveLeftMoreThanZoneCountReturnsFalse) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); - } - bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); - Assert::IsFalse(moreZonesInLayout); - } - }; - - TEST_CLASS (ZoneSetCalculateZonesUnitTests) - { - GUID m_id; - const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Custom; - winrt::com_ptr m_set; - - HMONITOR m_monitor{}; - const std::array m_popularMonitors{ - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1024, .bottom = 768 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 720 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 800 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 1024 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1366, .bottom = 768 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1440, .bottom = 900 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1536, .bottom = 864 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1600, .bottom = 900 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1920, .bottom = 1080 } } - }; - - void checkZones(const winrt::com_ptr& set, ZoneSetLayoutType type, size_t expectedCount, MONITORINFO monitorInfo) - { - auto zones = set->GetZones(); - Assert::AreEqual(expectedCount, zones.size()); - - int zoneId = 0; - for (const auto& zone : zones) - { - Assert::IsTrue(set->IsZoneEmpty(zoneId)); - - const auto& zoneRect = zone.second->GetZoneRect(); - Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); - Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); - - Assert::IsTrue(zoneRect.left < zoneRect.right, L"rect.left >= rect.right"); - Assert::IsTrue(zoneRect.top < zoneRect.bottom, L"rect.top >= rect.bottom"); - - if (type != ZoneSetLayoutType::Focus) - { - Assert::IsTrue(zoneRect.right <= monitorInfo.rcWork.right, L"right border is bigger than monitor work space"); - Assert::IsTrue(zoneRect.bottom <= monitorInfo.rcWork.bottom, L"bottom border is bigger than monitor work space"); - } - - zoneId++; - } - } - - TEST_METHOD_INITIALIZE(Init) - { - m_id = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, m_monitor, DefaultValues::SensitivityRadius); - m_set = MakeZoneSet(m_config); - } - - public: - TEST_METHOD (ValidValues) - { - const int spacing = 10; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - TEST_METHOD (InvalidMonitorInfo) - { - const int spacing = 10; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - MONITORINFO info{}; - auto result = set->CalculateZones(info.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (ZeroSpacing) - { - const int spacing = 0; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - - TEST_METHOD (LargeNegativeSpacing) - { - const int spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depends on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (HorizontallyBigSpacing) - { - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - const int spacing = monitorInfo.rcWork.right; - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depend on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (VerticallyBigSpacing) - { - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - const int spacing = monitorInfo.rcWork.bottom; - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depend on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (ZeroZoneCount) - { - const int spacing = 10; - const int zoneCount = 0; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - } - - TEST_METHOD (BigZoneCount) - { - const int zoneCount = 128; //editor limit - const int spacing = 0; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - }; - - TEST_CLASS(ZoneIndexSetUnitTests) - { - TEST_METHOD (BitmaskFromIndexSetTest) - { - // prepare - ZoneIndexSet set {0, 64}; - - // test - ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); - Assert::AreEqual(static_cast(1), bitmask.part1); - Assert::AreEqual(static_cast(1), bitmask.part2); - } - - TEST_METHOD(BitmaskToIndexSet) - { - // prepare - ZoneIndexSetBitmask bitmask{ - .part1 = 1, - .part2 = 1, - }; - - // test - ZoneIndexSet set = bitmask.ToIndexSet(); - Assert::AreEqual(static_cast(2), set.size()); - Assert::AreEqual(static_cast(0), set[0]); - Assert::AreEqual(static_cast(64), set[1]); - } - - TEST_METHOD (BitmaskConvertTest) - { - // prepare - ZoneIndexSet set{ 53, 54, 55, 65, 66, 67 }; - - ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); - - // test - ZoneIndexSet actual = bitmask.ToIndexSet(); - Assert::AreEqual(set.size(), actual.size()); - for (int i = 0; i < set.size(); i++) - { - Assert::AreEqual(set[i], actual[i]); - } - } - - TEST_METHOD (BitmaskConvert2Test) - { - // prepare - ZoneIndexSet set; - for (int i = 0; i < 128; i++) - { - set.push_back(i); - } - - ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set); - - // test - ZoneIndexSet actual = bitmask.ToIndexSet(); - - Assert::AreEqual(set.size(), actual.size()); - for (int i = 0; i < set.size(); i++) - { - Assert::AreEqual(set[i], actual[i]); - } - } - }; -}