[FancyZones] Window switch shortcut fix (#21426)

* rename Layout -> LayoutData

* simplify zone

* split ZoneSet: Layout

* refactoring

* split ZoneSet: LayoutWindows

* update trace

* split ZoneSet: remove ZoneSet

* fix initialization

* split unit tests

* remove unused

* warning

* nullptr  check

* use current rect

* update work area tests

* use current rect

* simplify

* more meaningful name

* dismiss

* safety checks

* resolve conflicts

* reassign windows after switching vd

* avoid double-processing for window on switching vd

* extend windows fix

* check if window is on current desktop before cycling

* separated extend

* not reinit layout windows
This commit is contained in:
Seraphima Zykova
2022-10-31 13:44:25 +02:00
committed by GitHub
parent 6431ccd370
commit ff290eef9d
43 changed files with 2194 additions and 2242 deletions

View File

@@ -25,7 +25,6 @@
#include <FancyZonesLib/MonitorUtils.h> #include <FancyZonesLib/MonitorUtils.h>
#include <FancyZonesLib/Settings.h> #include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h> #include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/ZoneSet.h>
#include <FancyZonesLib/WorkArea.h> #include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/WindowMoveHandler.h> #include <FancyZonesLib/WindowMoveHandler.h>
#include <FancyZonesLib/WindowUtils.h> #include <FancyZonesLib/WindowUtils.h>
@@ -102,9 +101,9 @@ public:
m_windowMoveHandler.MoveSizeUpdate(monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId())); 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) IFACEMETHODIMP_(void)
@@ -155,7 +154,7 @@ protected:
private: private:
void UpdateWorkAreas() noexcept; void UpdateWorkAreas() noexcept;
void CycleTabs(bool reverse) noexcept; void CycleWindows(bool reverse) noexcept;
bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept; bool OnSnapHotkey(DWORD vkCode) noexcept;
@@ -342,7 +341,7 @@ void FancyZones::MoveWindowIntoZone(HWND window, std::shared_ptr<WorkArea> workA
{ {
if (workArea) if (workArea)
{ {
Trace::FancyZones::SnapNewWindowIntoZone(workArea->ZoneSet()); Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
} }
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea); m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea);
AppZoneHistory::instance().UpdateProcessIdToHandleMap(window, workArea->UniqueId()); 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 // Open on active monitor if window wasn't zoned
if (openOnActiveMonitor && !movedToAppLastZone) 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<WPARAM>(HotkeyId::NextTab) || wparam == static_cast<WPARAM>(HotkeyId::PrevTab)) else if (wparam == static_cast<WPARAM>(HotkeyId::NextTab) || wparam == static_cast<WPARAM>(HotkeyId::PrevTab))
{ {
bool reverse = wparam == static_cast<WPARAM>(HotkeyId::PrevTab); bool reverse = wparam == static_cast<WPARAM>(HotkeyId::PrevTab);
CycleTabs(reverse); CycleWindows(reverse);
} }
} }
break; break;
@@ -630,7 +636,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (message == WM_PRIV_MOVESIZEEND) else if (message == WM_PRIV_MOVESIZEEND)
{ {
auto hwnd = reinterpret_cast<HWND>(wparam); auto hwnd = reinterpret_cast<HWND>(wparam);
MoveSizeEnd(hwnd, ptScreen); MoveSizeEnd(hwnd);
} }
else if (message == WM_PRIV_LOCATIONCHANGE) else if (message == WM_PRIV_LOCATIONCHANGE)
{ {
@@ -708,13 +714,10 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
UpdateWorkAreas(); 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(); auto window = GetForegroundWindow();
HMONITOR current = WorkAreaKeyFromWindow(window); HMONITOR current = WorkAreaKeyFromWindow(window);
@@ -790,7 +793,7 @@ void FancyZones::CycleTabs(bool reverse) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current); auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current);
if (workArea) 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); auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), *currMonitorInfo);
if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea)) 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; return true;
} }
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction). // 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 else
{ {
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
} }
return moved; 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); bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, workArea);
if (moved) if (moved)
{ {
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
} }
return moved; return moved;
} }
@@ -894,13 +897,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor); auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor);
if (workArea) if (workArea)
{ {
auto zoneSet = workArea->ZoneSet(); const auto& layout = workArea->GetLayout();
if (zoneSet) if (layout)
{ {
const auto zones = zoneSet->GetZones(); const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones) for (const auto& [zoneId, zone] : zones)
{ {
RECT zoneRect = zone->GetZoneRect(); RECT zoneRect = zone.GetZoneRect();
zoneRect.left += monitorRect.left; zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left; zoneRect.right += monitorRect.left;
@@ -929,7 +932,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
// Moving to another monitor succeeded // Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea);
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true; return true;
} }
@@ -942,13 +945,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current); auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current);
if (workArea) if (workArea)
{ {
auto zoneSet = workArea->ZoneSet(); const auto& layout = workArea->GetLayout();
if (zoneSet) if (layout)
{ {
const auto zones = zoneSet->GetZones(); const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones) for (const auto& [zoneId, zone] : zones)
{ {
RECT zoneRect = zone->GetZoneRect(); RECT zoneRect = zone.GetZoneRect();
zoneRect.left += currentMonitorRect.left; zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left; zoneRect.right += currentMonitorRect.left;
@@ -974,7 +977,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
// Moving to another monitor succeeded // Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea);
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true; return true;
} }
else else
@@ -1013,7 +1016,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle
bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea); bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea);
if (result) if (result)
{ {
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
} }
return result; return result;
} }
@@ -1022,7 +1025,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle
bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea); bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea);
if (result) if (result)
{ {
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
} }
return result; return result;
} }
@@ -1130,7 +1133,20 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
HMONITOR monitor = WorkAreaKeyFromWindow(window); HMONITOR monitor = WorkAreaKeyFromWindow(window);
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor); 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) if (vkCode == VK_UP || vkCode == VK_DOWN)
{ {
@@ -1142,6 +1158,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
} }
} }
} }
return false; return false;
} }
@@ -1193,7 +1210,7 @@ std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()); const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
for (const auto& [monitor, workArea] : activeWorkAreaMap) for (const auto& [monitor, workArea] : activeWorkAreaMap)
{ {
if (workArea->ZoneSet() != nullptr) if (workArea->GetLayout() != nullptr)
{ {
MONITORINFOEX mi; MONITORINFOEX mi;
mi.cbSize = sizeof(mi); mi.cbSize = sizeof(mi);

View File

@@ -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); auto processPath = get_process_path_waiting_uwp(window);
if (processPath.empty()) if (processPath.empty())

View File

@@ -54,7 +54,7 @@ public:
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
void UpdateProcessIdToHandleMap(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId); 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 SyncVirtualDesktops();
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops); void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);

View File

@@ -17,7 +17,7 @@
namespace namespace
{ {
// didn't use default constants since if they'll be changed later, it'll break this function // 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 && return layout.type == FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid &&
layout.zoneCount == 3 && layout.zoneCount == 3 &&
@@ -31,11 +31,11 @@ namespace JsonUtils
{ {
struct LayoutJSON struct LayoutJSON
{ {
static std::optional<Layout> FromJson(const json::JsonObject& json) static std::optional<LayoutData> FromJson(const json::JsonObject& json)
{ {
try try
{ {
Layout data{}; LayoutData data{};
auto idStr = json.GetNamedString(NonLocalizable::AppliedLayoutsIds::UuidID); auto idStr = json.GetNamedString(NonLocalizable::AppliedLayoutsIds::UuidID);
auto id = FancyZonesUtils::GuidFromString(idStr.c_str()); auto id = FancyZonesUtils::GuidFromString(idStr.c_str());
if (!id.has_value()) 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{}; json::JsonObject result{};
result.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value())); result.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value()));
@@ -141,7 +141,7 @@ namespace JsonUtils
public: public:
FancyZonesDataTypes::WorkAreaId workAreaId; FancyZonesDataTypes::WorkAreaId workAreaId;
Layout data{}; LayoutData data{};
bool hasResolutionInId = false; bool hasResolutionInId = false;
static std::optional<AppliedLayoutsJSON> FromJson(const json::JsonObject& json) static std::optional<AppliedLayoutsJSON> FromJson(const json::JsonObject& json)
@@ -427,7 +427,7 @@ void AppliedLayouts::RemoveDeletedVirtualDesktops(const std::vector<GUID>& activ
} }
} }
std::optional<Layout> AppliedLayouts::GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept std::optional<LayoutData> AppliedLayouts::GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept
{ {
auto iter = m_layouts.find(id); auto iter = m_layouts.find(id);
if (iter != m_layouts.end()) if (iter != m_layouts.end())
@@ -449,7 +449,7 @@ bool AppliedLayouts::IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id)
return iter != m_layouts.end(); 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); m_layouts[deviceId] = std::move(layout);
return true; return true;
@@ -467,8 +467,20 @@ bool AppliedLayouts::ApplyDefaultLayout(const FancyZonesDataTypes::WorkAreaId& d
return false; return false;
} }
// TODO: vertical or horizontal MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal;
m_layouts[deviceId] = DefaultLayouts::instance().GetDefaultLayout(); 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. // Saving default layout data doesn't make sense, since it's ignored on parsing.
// Given that default layouts are ignored when parsing, // Given that default layouts are ignored when parsing,

View File

@@ -4,7 +4,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <FancyZonesLib/FancyZonesData/Layout.h> #include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/ModuleConstants.h> #include <FancyZonesLib/ModuleConstants.h>
#include <common/SettingsAPI/FileWatcher.h> #include <common/SettingsAPI/FileWatcher.h>
@@ -35,7 +35,7 @@ namespace NonLocalizable
class AppliedLayouts class AppliedLayouts
{ {
public: public:
using TAppliedLayoutsMap = std::unordered_map<FancyZonesDataTypes::WorkAreaId, Layout>; using TAppliedLayoutsMap = std::unordered_map<FancyZonesDataTypes::WorkAreaId, LayoutData>;
static AppliedLayouts& instance(); static AppliedLayouts& instance();
@@ -55,12 +55,12 @@ public:
void SyncVirtualDesktops(); void SyncVirtualDesktops();
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops); void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);
std::optional<Layout> GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; std::optional<LayoutData> GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept;
const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept; const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept;
bool IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id) 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 ApplyDefaultLayout(const FancyZonesDataTypes::WorkAreaId& deviceId);
bool CloneLayout(const FancyZonesDataTypes::WorkAreaId& srcId, const FancyZonesDataTypes::WorkAreaId& dstId); bool CloneLayout(const FancyZonesDataTypes::WorkAreaId& srcId, const FancyZonesDataTypes::WorkAreaId& dstId);

View File

@@ -216,7 +216,7 @@ void CustomLayouts::LoadData()
} }
} }
std::optional<Layout> CustomLayouts::GetLayout(const GUID& id) const noexcept std::optional<LayoutData> CustomLayouts::GetLayout(const GUID& id) const noexcept
{ {
auto iter = m_layouts.find(id); auto iter = m_layouts.find(id);
if (iter == m_layouts.end()) if (iter == m_layouts.end())
@@ -225,7 +225,7 @@ std::optional<Layout> CustomLayouts::GetLayout(const GUID& id) const noexcept
} }
FancyZonesDataTypes::CustomLayoutData customLayout = iter->second; FancyZonesDataTypes::CustomLayoutData customLayout = iter->second;
Layout layout{ LayoutData layout{
.uuid = id, .uuid = id,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Custom .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom
}; };

View File

@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <FancyZonesLib/FancyZonesData/Layout.h> #include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/FancyZonesDataTypes.h> #include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/GuidUtils.h> #include <FancyZonesLib/GuidUtils.h>
#include <FancyZonesLib/ModuleConstants.h> #include <FancyZonesLib/ModuleConstants.h>
@@ -64,7 +64,7 @@ public:
void LoadData(); void LoadData();
std::optional<Layout> GetLayout(const GUID& id) const noexcept; std::optional<LayoutData> GetLayout(const GUID& id) const noexcept;
std::optional<FancyZonesDataTypes::CustomLayoutData> GetCustomLayoutData(const GUID& id) const noexcept; std::optional<FancyZonesDataTypes::CustomLayoutData> GetCustomLayoutData(const GUID& id) const noexcept;
const TCustomLayoutMap& GetAllLayouts() const noexcept; const TCustomLayoutMap& GetAllLayouts() const noexcept;

View File

@@ -34,11 +34,11 @@ namespace DefaultLayoutsJsonUtils
struct LayoutJSON struct LayoutJSON
{ {
static std::optional<Layout> FromJson(const json::JsonObject& json) static std::optional<LayoutData> FromJson(const json::JsonObject& json)
{ {
try try
{ {
Layout data{}; LayoutData data{};
auto idStr = json.GetNamedString(NonLocalizable::DefaultLayoutsIds::UuidID, L""); auto idStr = json.GetNamedString(NonLocalizable::DefaultLayoutsIds::UuidID, L"");
if (!idStr.empty()) 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{}; json::JsonObject result{};
result.SetNamedValue(NonLocalizable::DefaultLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value())); result.SetNamedValue(NonLocalizable::DefaultLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(data.uuid).value()));
@@ -81,7 +81,7 @@ namespace DefaultLayoutsJsonUtils
struct DefaultLayoutJSON struct DefaultLayoutJSON
{ {
MonitorConfiguraionType monitorConfigurationType{ MonitorConfiguraionType::Horizontal }; MonitorConfiguraionType monitorConfigurationType{ MonitorConfiguraionType::Horizontal };
Layout layout{}; LayoutData layout{};
static std::optional<DefaultLayoutJSON> FromJson(const json::JsonObject& json) static std::optional<DefaultLayoutJSON> 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); auto iter = m_layouts.find(type);
if (iter != m_layouts.end()) if (iter != m_layouts.end())
@@ -171,5 +171,5 @@ Layout DefaultLayouts::GetDefaultLayout(MonitorConfiguraionType type) const noex
return iter->second; return iter->second;
} }
return Layout{}; return LayoutData{};
} }

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <FancyZonesLib/FancyZonesData/Layout.h> #include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/ModuleConstants.h> #include <FancyZonesLib/ModuleConstants.h>
#include <common/SettingsAPI/FileWatcher.h> #include <common/SettingsAPI/FileWatcher.h>
@@ -31,7 +31,7 @@ enum class MonitorConfiguraionType
class DefaultLayouts class DefaultLayouts
{ {
public: public:
using TDefaultLayoutsContainer = std::map<MonitorConfiguraionType, Layout>; using TDefaultLayoutsContainer = std::map<MonitorConfiguraionType, LayoutData>;
static DefaultLayouts& instance(); static DefaultLayouts& instance();
@@ -46,7 +46,7 @@ public:
void LoadData(); void LoadData();
Layout GetDefaultLayout(MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal) const noexcept; LayoutData GetDefaultLayout(MonitorConfiguraionType type = MonitorConfiguraionType::Horizontal) const noexcept;
private: private:
DefaultLayouts(); DefaultLayouts();

View File

@@ -5,7 +5,7 @@
#include <FancyZonesLib/FancyZonesData/LayoutDefaults.h> #include <FancyZonesLib/FancyZonesData/LayoutDefaults.h>
#include <FancyZonesLib/FancyZonesDataTypes.h> #include <FancyZonesLib/FancyZonesDataTypes.h>
struct Layout struct LayoutData
{ {
GUID uuid = GUID_NULL; GUID uuid = GUID_NULL;
FancyZonesDataTypes::ZoneSetLayoutType type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid; FancyZonesDataTypes::ZoneSetLayoutType type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid;
@@ -15,7 +15,7 @@ struct Layout
int sensitivityRadius = DefaultValues::SensitivityRadius; 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 && return lhs.uuid == rhs.uuid &&
lhs.type == rhs.type && lhs.type == rhs.type &&

View File

@@ -10,11 +10,11 @@ namespace JsonUtils
{ {
struct TemplateLayoutJSON struct TemplateLayoutJSON
{ {
static std::optional<Layout> FromJson(const json::JsonObject& json) static std::optional<LayoutData> FromJson(const json::JsonObject& json)
{ {
try try
{ {
Layout data; LayoutData data;
data.uuid = GUID_NULL; data.uuid = GUID_NULL;
data.type = FancyZonesDataTypes::TypeFromString(std::wstring{ json.GetNamedString(NonLocalizable::LayoutTemplatesIds::TypeID) }); data.type = FancyZonesDataTypes::TypeFromString(std::wstring{ json.GetNamedString(NonLocalizable::LayoutTemplatesIds::TypeID) });
@@ -32,9 +32,9 @@ namespace JsonUtils
} }
}; };
std::vector<Layout> ParseJson(const json::JsonObject& json) std::vector<LayoutData> ParseJson(const json::JsonObject& json)
{ {
std::vector<Layout> vec{}; std::vector<LayoutData> vec{};
auto layouts = json.GetNamedArray(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID); auto layouts = json.GetNamedArray(NonLocalizable::LayoutTemplatesIds::LayoutTemplatesArrayID);
for (uint32_t i = 0; i < layouts.Size(); ++i) for (uint32_t i = 0; i < layouts.Size(); ++i)
@@ -85,7 +85,7 @@ void LayoutTemplates::LoadData()
} }
} }
std::optional<Layout> LayoutTemplates::GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept std::optional<LayoutData> LayoutTemplates::GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept
{ {
for (const auto& layout : m_layouts) for (const auto& layout : m_layouts)
{ {

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <FancyZonesLib/FancyZonesData/Layout.h> #include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/ModuleConstants.h> #include <FancyZonesLib/ModuleConstants.h>
#include <common/SettingsAPI/FileWatcher.h> #include <common/SettingsAPI/FileWatcher.h>
@@ -35,12 +35,12 @@ public:
void LoadData(); void LoadData();
std::optional<Layout> GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept; std::optional<LayoutData> GetLayout(FancyZonesDataTypes::ZoneSetLayoutType type) const noexcept;
private: private:
LayoutTemplates(); LayoutTemplates();
~LayoutTemplates() = default; ~LayoutTemplates() = default;
std::unique_ptr<FileWatcher> m_fileWatcher; std::unique_ptr<FileWatcher> m_fileWatcher;
std::vector<Layout> m_layouts; std::vector<LayoutData> m_layouts;
}; };

View File

@@ -42,7 +42,7 @@
<ClInclude Include="FancyZones.h" /> <ClInclude Include="FancyZones.h" />
<ClInclude Include="FancyZonesDataTypes.h" /> <ClInclude Include="FancyZonesDataTypes.h" />
<ClInclude Include="FancyZonesData\DefaultLayouts.h" /> <ClInclude Include="FancyZonesData\DefaultLayouts.h" />
<ClInclude Include="FancyZonesData\Layout.h" /> <ClInclude Include="FancyZonesData\LayoutData.h" />
<ClInclude Include="FancyZonesData\LayoutDefaults.h" /> <ClInclude Include="FancyZonesData\LayoutDefaults.h" />
<ClInclude Include="FancyZonesData\LayoutTemplates.h" /> <ClInclude Include="FancyZonesData\LayoutTemplates.h" />
<ClInclude Include="FancyZonesWindowProcessing.h" /> <ClInclude Include="FancyZonesWindowProcessing.h" />
@@ -53,7 +53,9 @@
<ClInclude Include="JsonHelpers.h" /> <ClInclude Include="JsonHelpers.h" />
<ClInclude Include="KeyState.h" /> <ClInclude Include="KeyState.h" />
<ClInclude Include="FancyZonesData\LayoutHotkeys.h" /> <ClInclude Include="FancyZonesData\LayoutHotkeys.h" />
<ClInclude Include="Layout.h" />
<ClInclude Include="LayoutConfigurator.h" /> <ClInclude Include="LayoutConfigurator.h" />
<ClInclude Include="LayoutAssignedWindows.h" />
<ClInclude Include="ModuleConstants.h" /> <ClInclude Include="ModuleConstants.h" />
<ClInclude Include="MonitorUtils.h" /> <ClInclude Include="MonitorUtils.h" />
<ClInclude Include="MonitorWorkAreaHandler.h" /> <ClInclude Include="MonitorWorkAreaHandler.h" />
@@ -73,7 +75,6 @@
<ClInclude Include="Zone.h" /> <ClInclude Include="Zone.h" />
<ClInclude Include="Colors.h" /> <ClInclude Include="Colors.h" />
<ClInclude Include="ZoneIndexSetBitmask.h" /> <ClInclude Include="ZoneIndexSetBitmask.h" />
<ClInclude Include="ZoneSet.h" />
<ClInclude Include="WorkArea.h" /> <ClInclude Include="WorkArea.h" />
<ClInclude Include="ZonesOverlay.h" /> <ClInclude Include="ZonesOverlay.h" />
</ItemGroup> </ItemGroup>
@@ -104,7 +105,9 @@
<ClCompile Include="FancyZonesData\LayoutHotkeys.cpp"> <ClCompile Include="FancyZonesData\LayoutHotkeys.cpp">
<PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<ClCompile Include="Layout.cpp" />
<ClCompile Include="LayoutConfigurator.cpp" /> <ClCompile Include="LayoutConfigurator.cpp" />
<ClCompile Include="LayoutAssignedWindows.cpp" />
<ClCompile Include="MonitorUtils.cpp" /> <ClCompile Include="MonitorUtils.cpp" />
<ClCompile Include="MonitorWorkAreaHandler.cpp" /> <ClCompile Include="MonitorWorkAreaHandler.cpp" />
<ClCompile Include="OnThreadExecutor.cpp" /> <ClCompile Include="OnThreadExecutor.cpp" />
@@ -119,7 +122,6 @@
<ClCompile Include="WindowMoveHandler.cpp" /> <ClCompile Include="WindowMoveHandler.cpp" />
<ClCompile Include="WindowUtils.cpp" /> <ClCompile Include="WindowUtils.cpp" />
<ClCompile Include="Zone.cpp" /> <ClCompile Include="Zone.cpp" />
<ClCompile Include="ZoneSet.cpp" />
<ClCompile Include="WorkArea.cpp" /> <ClCompile Include="WorkArea.cpp" />
<ClCompile Include="ZonesOverlay.cpp" /> <ClCompile Include="ZonesOverlay.cpp" />
</ItemGroup> </ItemGroup>

View File

@@ -30,9 +30,6 @@
<ClInclude Include="Zone.h"> <ClInclude Include="Zone.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="ZoneSet.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WorkArea.h"> <ClInclude Include="WorkArea.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@@ -99,6 +96,12 @@
<ClInclude Include="ModuleConstants.h"> <ClInclude Include="ModuleConstants.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="FancyZonesData\LayoutTemplates.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FancyZonesData\LayoutDefaults.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ZoneIndexSetBitmask.h"> <ClInclude Include="ZoneIndexSetBitmask.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@@ -129,9 +132,6 @@
<ClInclude Include="FancyZonesData\DefaultLayouts.h"> <ClInclude Include="FancyZonesData\DefaultLayouts.h">
<Filter>Header Files\FancyZonesData</Filter> <Filter>Header Files\FancyZonesData</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="FancyZonesData\Layout.h">
<Filter>Header Files\FancyZonesData</Filter>
</ClInclude>
<ClInclude Include="FancyZonesData\LayoutDefaults.h"> <ClInclude Include="FancyZonesData\LayoutDefaults.h">
<Filter>Header Files\FancyZonesData</Filter> <Filter>Header Files\FancyZonesData</Filter>
</ClInclude> </ClInclude>
@@ -144,6 +144,15 @@
<ClInclude Include="EditorParameters.h"> <ClInclude Include="EditorParameters.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="LayoutAssignedWindows.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Layout.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FancyZonesData\LayoutData.h">
<Filter>Header Files\FancyZonesData</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
@@ -152,9 +161,6 @@
<ClCompile Include="Zone.cpp"> <ClCompile Include="Zone.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ZoneSet.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WorkArea.cpp"> <ClCompile Include="WorkArea.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@@ -236,6 +242,12 @@
<ClCompile Include="FancyZonesData\LayoutTemplates.cpp"> <ClCompile Include="FancyZonesData\LayoutTemplates.cpp">
<Filter>Source Files\FancyZonesData</Filter> <Filter>Source Files\FancyZonesData</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Layout.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LayoutAssignedWindows.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />

View File

@@ -83,6 +83,17 @@ ZoneIndexSet FancyZonesWindowProperties::RetrieveZoneIndexProperty(HWND window)
return bitmask.ToIndexSet(); 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<size_t> FancyZonesWindowProperties::GetTabSortKeyWithinZone(HWND window) std::optional<size_t> FancyZonesWindowProperties::GetTabSortKeyWithinZone(HWND window)
{ {
auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone);

View File

@@ -10,6 +10,7 @@ namespace ZonedWindowProperties
const wchar_t PropertyRestoreSizeID[] = L"FancyZones_RestoreSize"; const wchar_t PropertyRestoreSizeID[] = L"FancyZones_RestoreSize";
const wchar_t PropertyRestoreOriginID[] = L"FancyZones_RestoreOrigin"; const wchar_t PropertyRestoreOriginID[] = L"FancyZones_RestoreOrigin";
const wchar_t PropertyCornerPreference[] = L"FancyZones_CornerPreference"; const wchar_t PropertyCornerPreference[] = L"FancyZones_CornerPreference";
const wchar_t PropertyMovedOnOpening[] = L"FancyZones_MovedOnOpening";
const wchar_t MultiMonitorName[] = L"FancyZones"; const wchar_t MultiMonitorName[] = L"FancyZones";
const wchar_t MultiMonitorInstance[] = L"MultiMonitorDevice"; const wchar_t MultiMonitorInstance[] = L"MultiMonitorDevice";
@@ -21,6 +22,9 @@ namespace FancyZonesWindowProperties
void RemoveZoneIndexProperty(HWND window); void RemoveZoneIndexProperty(HWND window);
ZoneIndexSet RetrieveZoneIndexProperty(HWND window); ZoneIndexSet RetrieveZoneIndexProperty(HWND window);
void StampMovedOnOpeningProperty(HWND window);
bool RetreiveMovedOnOpeningProperty(HWND window);
std::optional<size_t> GetTabSortKeyWithinZone(HWND window); std::optional<size_t> GetTabSortKeyWithinZone(HWND window);
void SetTabSortKeyWithinZone(HWND window, std::optional<size_t> tabSortKeyWithinZone); void SetTabSortKeyWithinZone(HWND window, std::optional<size_t> tabSortKeyWithinZone);
} }

View File

@@ -0,0 +1,334 @@
#include "pch.h"
#include "Layout.h"
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/LayoutConfigurator.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/WindowUtils.h>
#include <common/logger/logger.h>
namespace ZoneSelectionAlgorithms
{
constexpr int OVERLAPPING_CENTERS_SENSITIVITY = 75;
template<class CompareF>
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<ZoneIndex>(pt.y) - overlap.top) * capturedZones.size() / height;
}
else
{
zoneIndex = (static_cast<ZoneIndex>(pt.x) - overlap.left) * capturedZones.size() / width;
}
zoneIndex = std::clamp(zoneIndex, ZoneIndex(0), static_cast<ZoneIndex>(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;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/util.h>
#include <FancyZonesLib/LayoutConfigurator.h> // 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{};
};

View File

@@ -0,0 +1,199 @@
#include "pch.h"
#include "LayoutAssignedWindows.h"
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowUtils.h>
LayoutAssignedWindows::LayoutAssignedWindows()
{
m_extendData = std::make_unique<ExtendWindowModeData>();
}
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::ExtendWindowModeData>& LayoutAssignedWindows::ExtendWindowData()
{
return m_extendData;
}
void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional<size_t> 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);
}
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include <FancyZonesLib/Zone.h>
class LayoutAssignedWindows
{
public:
struct ExtendWindowModeData
{
std::map<HWND, ZoneIndexSet> windowInitialIndexSet;
std::map<HWND, ZoneIndex> 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<ExtendWindowModeData>& ExtendWindowData();
private:
std::map<HWND, ZoneIndexSet> m_windowIndexSet{};
std::map<ZoneIndexSet, std::vector<HWND>> m_windowsByIndexSets{};
std::unique_ptr<ExtendWindowModeData> m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition
void InsertWindowIntoZone(HWND window, std::optional<size_t> tabSortKeyWithinZone, const ZoneIndexSet& indexSet);
HWND GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept;
};

View File

@@ -92,15 +92,15 @@ namespace
}; };
} }
bool AddZone(winrt::com_ptr<IZone> zone, ZonesMap& zones) noexcept bool AddZone(Zone zone, ZonesMap& zones) noexcept
{ {
auto zoneId = zone->Id(); auto zoneId = zone.Id();
if (zones.contains(zoneId)) if (zones.contains(zoneId))
{ {
return false; return false;
} }
zones[zoneId] = zone; zones.insert({ zoneId, std::move(zone) });
return true; return true;
} }
@@ -169,8 +169,8 @@ ZonesMap CalculateGridZones(FancyZonesUtils::Rect workArea, FancyZonesDataTypes:
left += col == 0 ? spacing : spacing / 2; left += col == 0 ? spacing : spacing / 2;
right -= maxCol == static_cast<int64_t>(gridLayoutInfo.columns()) - 1 ? spacing : spacing / 2; right -= maxCol == static_cast<int64_t>(gridLayoutInfo.columns()) - 1 ? spacing : spacing / 2;
auto zone = MakeZone(RECT{ left, top, right, bottom }, i); Zone zone(RECT{ left, top, right, bottom }, i);
if (zone) if (zone.IsValid())
{ {
if (!AddZone(zone, zones)) if (!AddZone(zone, zones))
{ {
@@ -207,8 +207,8 @@ ZonesMap LayoutConfigurator::Focus(FancyZonesUtils::Rect workArea, int zoneCount
for (int i = 0; i < zoneCount; i++) for (int i = 0; i < zoneCount; i++)
{ {
auto zone = MakeZone(focusZoneRect, zones.size()); Zone zone(focusZoneRect, zones.size());
if (zone) if (zone.IsValid())
{ {
if (!AddZone(zone, zones)) if (!AddZone(zone, zones))
{ {
@@ -251,8 +251,8 @@ ZonesMap LayoutConfigurator::Rows(FancyZonesUtils::Rect workArea, int zoneCount,
right = totalWidth + spacing; right = totalWidth + spacing;
bottom = top + (zoneIndex + 1) * totalHeight / zoneCount - zoneIndex * totalHeight / zoneCount; bottom = top + (zoneIndex + 1) * totalHeight / zoneCount - zoneIndex * totalHeight / zoneCount;
auto zone = MakeZone(RECT{ left, top, right, bottom }, zones.size()); Zone zone(RECT{ left, top, right, bottom }, zones.size());
if (zone) if (zone.IsValid())
{ {
if (!AddZone(zone, zones)) 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; right = left + (zoneIndex + 1) * totalWidth / zoneCount - zoneIndex * totalWidth / zoneCount;
bottom = totalHeight + spacing; bottom = totalHeight + spacing;
auto zone = MakeZone(RECT{ left, top, right, bottom }, zones.size()); Zone zone(RECT{ left, top, right, bottom }, zones.size());
if (zone) if (zone.IsValid())
{ {
if (!AddZone(zone, zones)) if (!AddZone(zone, zones))
{ {
@@ -404,8 +404,8 @@ ZonesMap LayoutConfigurator::Custom(FancyZonesUtils::Rect workArea, HMONITOR mon
DPIAware::Convert(monitor, x, y); DPIAware::Convert(monitor, x, y);
DPIAware::Convert(monitor, zoneWidth, zoneHeight); DPIAware::Convert(monitor, zoneWidth, zoneHeight);
auto zone = MakeZone(RECT{ static_cast<long>(x), static_cast<long>(y), static_cast<long>(x + zoneWidth), static_cast<long>(y + zoneHeight) }, zones.size()); Zone zone(RECT{ static_cast<long>(x), static_cast<long>(y), static_cast<long>(x + zoneWidth), static_cast<long>(y + zoneHeight) }, zones.size());
if (zone) if (zone.IsValid())
{ {
if (!AddZone(zone, zones)) if (!AddZone(zone, zones))
{ {

View File

@@ -4,7 +4,7 @@
#include <FancyZonesLib/util.h> #include <FancyZonesLib/util.h>
// Mapping zone id to zone // Mapping zone id to zone
using ZonesMap = std::map<ZoneIndex, winrt::com_ptr<IZone>>; using ZonesMap = std::map<ZoneIndex, Zone>;
namespace FancyZonesDataTypes namespace FancyZonesDataTypes
{ {

View File

@@ -132,10 +132,10 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const
auto workArea = workAreaMap.find(monitor); auto workArea = workAreaMap.find(monitor);
if (workArea != workAreaMap.end()) if (workArea != workAreaMap.end())
{ {
const auto zoneSet = workArea->second->ZoneSet(); const auto& layoutWindows = workArea->second->GetLayoutWindows();
if (zoneSet) 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<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept void WindowMoveHandler::MoveSizeEnd(HWND window, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept
{ {
if (window != m_draggedWindow) if (window != m_draggedWindow)
{ {
@@ -235,7 +235,7 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st
} }
else else
{ {
workArea->MoveSizeEnd(m_draggedWindow, ptScreen); workArea->MoveSizeEnd(m_draggedWindow);
} }
} }
else else
@@ -261,13 +261,13 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st
if (workArea != workAreaMap.end()) if (workArea != workAreaMap.end())
{ {
const auto workAreaPtr = workArea->second; const auto workAreaPtr = workArea->second;
const auto zoneSet = workAreaPtr->ZoneSet(); const auto& layout = workAreaPtr->GetLayout();
if (zoneSet) if (layout)
{ {
wil::unique_cotaskmem_string guidString; auto guidStr = FancyZonesUtils::GuidToString(layout->Id());
if (SUCCEEDED_LOG(StringFromCLSID(zoneSet->Id(), &guidString))) 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_map<HMONITOR
continue; continue;
} }
for (const auto& [monitor, workArea] : activeWorkAreas) auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor && activeWorkAreas.contains(monitor))
{ {
if (MonitorFromWindow(window, MONITOR_DEFAULTTONULL) == monitor) activeWorkAreas.at(monitor)->MoveWindowIntoZoneByIndexSet(window, zoneIndexSet);
{
workArea->MoveWindowIntoZoneByIndexSet(window, zoneIndexSet);
}
} }
} }
} }

View File

@@ -16,7 +16,7 @@ public:
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept; void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept;
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept; void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept;
void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept; void MoveSizeEnd(HWND window, const std::unordered_map<HMONITOR, std::shared_ptr<WorkArea>>& workAreaMap) noexcept;
void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, std::shared_ptr<WorkArea> workArea) noexcept; void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, std::shared_ptr<WorkArea> workArea) noexcept;
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, std::shared_ptr<WorkArea> workArea) noexcept; bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, std::shared_ptr<WorkArea> workArea) noexcept;

View File

@@ -127,12 +127,17 @@ HRESULT WorkArea::MoveSizeEnter(HWND window) noexcept
m_highlightZone = {}; m_highlightZone = {};
m_initialHighlightZone = {}; m_initialHighlightZone = {};
ShowZonesOverlay(); ShowZonesOverlay();
Trace::WorkArea::MoveOrResizeStarted(m_zoneSet); Trace::WorkArea::MoveOrResizeStarted(m_layout.get(), m_layoutWindows.get());
return S_OK; return S_OK;
} }
HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept
{ {
if (!m_layout)
{
return -1;
}
bool redraw = false; bool redraw = false;
POINT ptClient = ptScreen; POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_window, &ptClient, 1); MapWindowPoints(nullptr, m_window, &ptClient, 1);
@@ -150,7 +155,7 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s
} }
else else
{ {
highlightZone = m_zoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); highlightZone = m_layout->GetCombinedZoneRange(m_initialHighlightZone, highlightZone);
} }
} }
else else
@@ -167,33 +172,24 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s
redraw = true; 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; return S_OK;
} }
HRESULT WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept HRESULT WorkArea::MoveSizeEnd(HWND window) noexcept
{ {
if (m_windowMoveSize != window) if (m_windowMoveSize != window)
{ {
return E_INVALIDARG; return E_INVALIDARG;
} }
if (m_zoneSet) MoveWindowIntoZoneByIndexSet(window, m_highlightZone);
{
POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_window, &ptClient, 1);
m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone);
if (!FancyZonesWindowUtils::HasVisibleOwner(window)) Trace::WorkArea::MoveOrResizeEnd(m_layout.get(), m_layoutWindows.get());
{
SaveWindowProcessToZoneIndex(window);
}
}
Trace::WorkArea::MoveOrResizeEnd(m_zoneSet);
HideZonesOverlay(); HideZonesOverlay();
m_windowMoveSize = nullptr; m_windowMoveSize = nullptr;
@@ -207,80 +203,264 @@ void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept
void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) 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 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 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<bool> usedZoneIndices(zones.size(), false);
auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
for (ZoneIndex id : windowZones)
{
usedZoneIndices[id] = true;
}
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
for (const auto& [zoneId, zone] : zones)
{
if (!usedZoneIndices[zoneId])
{ {
zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
freeZoneIndices.emplace_back(zoneId);
}
}
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); SaveWindowProcessToZoneIndex(window);
Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
return true; return true;
} }
} }
return false; return false;
} }
bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept 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<bool> usedZoneIndices(zones.size(), false);
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
// If selectManyZones = true for the second time, use the last zone into which we moved
// instead of the window rect and enable moving to all zones except the old one
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); usedZoneIndices[idx] = true;
return 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; return false;
} }
void WorkArea::SaveWindowProcessToZoneIndex(HWND window) noexcept 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()) if (zoneIndexSet.size())
{ {
OLECHAR* guidString; auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (StringFromCLSID(m_zoneSet->Id(), &guidString) == S_OK) 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 ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept
{ {
if (m_zoneSet) if (m_layout)
{ {
wil::unique_cotaskmem_string zoneSetId; auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (SUCCEEDED(StringFromCLSID(m_zoneSet->Id(), &zoneSetId))) if (guidStr.has_value())
{ {
return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, zoneSetId.get()); return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value());
} }
else else
{ {
@@ -297,10 +477,10 @@ ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept
void WorkArea::ShowZonesOverlay() noexcept void WorkArea::ShowZonesOverlay() noexcept
{ {
if (m_window) if (m_window && m_layout)
{ {
SetAsTopmostWindow(); 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(); m_zonesOverlay->Show();
} }
} }
@@ -324,37 +504,37 @@ void WorkArea::UpdateActiveZoneSet() noexcept
AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId);
} }
CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm); CalculateZoneSet();
if (m_window && m_zoneSet) if (m_window && m_layout)
{ {
m_highlightZone.clear(); 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 void WorkArea::ClearSelectedZones() noexcept
{ {
if (m_highlightZone.size()) if (m_highlightZone.size() && m_layout)
{ {
m_highlightZone.clear(); 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 void WorkArea::FlashZones() noexcept
{ {
if (m_window) if (m_window && m_layout)
{ {
SetAsTopmostWindow(); 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(); 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); const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
if (!appliedLayout.has_value()) 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; return;
} }
auto zoneSet = MakeZoneSet(ZoneSetConfig( m_layout = std::make_unique<Layout>(appliedLayout.value());
appliedLayout->uuid, m_layout->Init(m_workAreaRect, m_monitor);
appliedLayout->type,
m_monitor,
appliedLayout->sensitivityRadius,
overlappingAlgorithm));
bool showSpacing = appliedLayout->showSpacing; if (!m_layoutWindows)
int spacing = showSpacing ? appliedLayout->spacing : 0; {
int zoneCount = appliedLayout->zoneCount; m_layoutWindows = std::make_unique<LayoutAssignedWindows>();
}
zoneSet->CalculateZones(m_workAreaRect, zoneCount, spacing);
UpdateActiveZoneSet(zoneSet.get());
}
void WorkArea::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept
{
m_zoneSet.copy_from(zoneSet);
} }
LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept 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 ZoneIndexSet WorkArea::ZonesFromPoint(POINT pt) noexcept
{ {
if (m_zoneSet) if (m_layout)
{ {
return m_zoneSet->ZonesFromPoint(pt); return m_layout->ZonesFromPoint(pt);
} }
return {}; return {};
} }

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include <FancyZonesLib/FancyZonesDataTypes.h> #include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/ZoneSet.h> #include <FancyZonesLib/Layout.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
#include <FancyZonesLib/util.h> #include <FancyZonesLib/util.h>
class ZonesOverlay; class ZonesOverlay;
@@ -55,13 +56,14 @@ public:
} }
FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; } FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; }
IZoneSet* ZoneSet() const noexcept { return m_zoneSet.get(); } const std::unique_ptr<Layout>& GetLayout() const noexcept { return m_layout; }
const std::unique_ptr<LayoutAssignedWindows>& GetLayoutWindows() const noexcept { return m_layoutWindows; }
ZoneIndexSet GetWindowZoneIndexes(HWND window) const noexcept; ZoneIndexSet GetWindowZoneIndexes(HWND window) const noexcept;
HRESULT MoveSizeEnter(HWND window) noexcept; HRESULT MoveSizeEnter(HWND window) noexcept;
HRESULT MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) 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 MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept;
void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept; void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept;
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept;
@@ -76,7 +78,7 @@ public:
void FlashZones() noexcept; void FlashZones() noexcept;
void ClearSelectedZones() noexcept; void ClearSelectedZones() noexcept;
void CycleTabs(HWND window, bool reverse) noexcept; void CycleWindows(HWND window, bool reverse) noexcept;
void LogInitializationError(); void LogInitializationError();
@@ -86,8 +88,7 @@ protected:
private: private:
bool InitWindow(HINSTANCE hinstance) noexcept; bool InitWindow(HINSTANCE hinstance) noexcept;
void InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) noexcept; void InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) noexcept;
void CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept; void CalculateZoneSet() noexcept;
void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept;
LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept;
ZoneIndexSet ZonesFromPoint(POINT pt) noexcept; ZoneIndexSet ZonesFromPoint(POINT pt) noexcept;
void SetAsTopmostWindow() noexcept; void SetAsTopmostWindow() noexcept;
@@ -97,7 +98,8 @@ private:
const FancyZonesDataTypes::WorkAreaId m_uniqueId; const FancyZonesDataTypes::WorkAreaId m_uniqueId;
HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area.
HWND m_windowMoveSize{}; HWND m_windowMoveSize{};
winrt::com_ptr<IZoneSet> m_zoneSet; std::unique_ptr<Layout> m_layout;
std::unique_ptr<LayoutAssignedWindows> m_layoutWindows;
ZoneIndexSet m_initialHighlightZone; ZoneIndexSet m_initialHighlightZone;
ZoneIndexSet m_highlightZone; ZoneIndexSet m_highlightZone;
WPARAM m_keyLast{}; WPARAM m_keyLast{};

View File

@@ -1,54 +1,45 @@
#include "pch.h" #include "pch.h"
#include <Shellscalingapi.h>
#include <common/display/dpi_aware.h>
#include <common/display/monitors.h>
#include "Zone.h" #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, IZone> 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<IZone> MakeZone(const RECT& zoneRect, const ZoneIndex zoneId) noexcept
{
if (ValidateZoneRect(zoneRect) && zoneId >= 0)
{
return winrt::make_self<Zone>(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;
}

View File

@@ -11,20 +11,21 @@ using ZoneIndexSet = std::vector<ZoneIndex>;
/** /**
* Class representing one zone inside applied zone layout, which is basically wrapper around rectangle structure. * 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
{ {
/** public:
* @returns Zone coordinates (top-left and bottom-right corner) represented as RECT structure. Zone(const RECT& zoneRect, const ZoneIndex zoneIndex);
*/ Zone(const Zone& other);
IFACEMETHOD_(RECT, GetZoneRect)() const = 0; ~Zone() = default;
/**
* @returns Zone area calculated from zone rect
*/
IFACEMETHOD_(long, GetZoneArea)() const = 0;
/**
* @returns Zone identifier.
*/
IFACEMETHOD_(ZoneIndex, Id)() const = 0;
};
winrt::com_ptr<IZone> 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;
};

View File

@@ -1,770 +0,0 @@
#include "pch.h"
#include "ZoneSet.h"
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/WindowUtils.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
using namespace FancyZonesUtils;
namespace
{
constexpr int OVERLAPPING_CENTERS_SENSITIVITY = 75;
}
struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>
{
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<size_t> 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<class CompareF>
ZoneIndexSet ZoneSelectPriority(const ZoneIndexSet& capturedZones, CompareF compare) const;
ZonesMap m_zones;
std::map<HWND, ZoneIndexSet> m_windowIndexSet;
std::map<ZoneIndexSet, std::vector<HWND>> m_windowsByIndexSets;
// Needed for ExtendWindowByDirectionAndPosition
std::map<HWND, ZoneIndexSet> m_windowInitialIndexSet;
std::map<HWND, ZoneIndex> 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<bool> usedZoneIndices(m_zones.size(), false);
for (ZoneIndex id : GetZoneIndexSetFromWindow(window))
{
usedZoneIndices[id] = true;
}
std::vector<RECT> 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<bool> usedZoneIndices(m_zones.size(), false);
std::vector<RECT> zoneRects;
ZoneIndexSet freeZoneIndices;
// If selectManyZones = true for the second time, use the last zone into which we moved
// instead of the window rect and enable moving to all zones except the old one
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<size_t> 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<int64_t>(pt.y) - overlap.top) * capturedZones.size() / height;
}
else
{
zoneIndex = (static_cast<int64_t>(pt.x) - overlap.left) * capturedZones.size() / width;
}
zoneIndex = std::clamp(zoneIndex, ZoneIndex(0), static_cast<ZoneIndex>(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<class CompareF>
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<IZoneSet> MakeZoneSet(ZoneSetConfig const& config) noexcept
{
return winrt::make_self<ZoneSet>(config);
}

View File

@@ -1,181 +0,0 @@
#pragma once
#include <FancyZonesLib/LayoutConfigurator.h>
#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<IZoneSet> MakeZoneSet(ZoneSetConfig const& config) noexcept;

View File

@@ -303,19 +303,14 @@ void ZonesOverlay::DrawActiveZoneSet(const ZonesMap& zones,
// First draw the inactive zones // First draw the inactive zones
for (const auto& [zoneId, zone] : zones) for (const auto& [zoneId, zone] : zones)
{ {
if (!zone)
{
continue;
}
if (!isHighlighted[zoneId]) if (!isHighlighted[zoneId])
{ {
DrawableRect drawableRect{ DrawableRect drawableRect{
.rect = ConvertRect(zone->GetZoneRect()), .rect = ConvertRect(zone.GetZoneRect()),
.borderColor = borderColor, .borderColor = borderColor,
.fillColor = inactiveColor, .fillColor = inactiveColor,
.textColor = numberColor, .textColor = numberColor,
.id = zone->Id(), .id = zone.Id(),
.showText = showZoneText .showText = showZoneText
}; };
@@ -326,19 +321,14 @@ void ZonesOverlay::DrawActiveZoneSet(const ZonesMap& zones,
// Draw the active zones on top of the inactive zones // Draw the active zones on top of the inactive zones
for (const auto& [zoneId, zone] : zones) for (const auto& [zoneId, zone] : zones)
{ {
if (!zone)
{
continue;
}
if (isHighlighted[zoneId]) if (isHighlighted[zoneId])
{ {
DrawableRect drawableRect{ DrawableRect drawableRect{
.rect = ConvertRect(zone->GetZoneRect()), .rect = ConvertRect(zone.GetZoneRect()),
.borderColor = borderColor, .borderColor = borderColor,
.fillColor = highlightColor, .fillColor = highlightColor,
.textColor = numberColor, .textColor = numberColor,
.id = zone->Id(), .id = zone.Id(),
.showText = showZoneText .showText = showZoneText
}; };

View File

@@ -9,9 +9,9 @@
#include "util.h" #include "util.h"
#include "Zone.h" #include "Zone.h"
#include "ZoneSet.h"
#include "FancyZones.h" #include "FancyZones.h"
#include "Colors.h" #include "Colors.h"
#include "LayoutConfigurator.h"
class ZonesOverlay class ZonesOverlay
{ {

View File

@@ -1,6 +1,7 @@
#include "pch.h" #include "pch.h"
#include "trace.h" #include "trace.h"
#include "FancyZonesLib/ZoneSet.h" #include "FancyZonesLib/Layout.h"
#include "FancyZonesLib/LayoutAssignedWindows.h"
#include "FancyZonesLib/Settings.h" #include "FancyZonesLib/Settings.h"
#include "FancyZonesData/AppZoneHistory.h" #include "FancyZonesData/AppZoneHistory.h"
#include "FancyZonesLib/FancyZonesData/AppliedLayouts.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; ZoneSetInfo info;
if (set) if (layout && layoutWindows)
{ {
auto zones = set->GetZones(); auto zones = layout->Zones();
info.NumberOfZones = zones.size(); info.NumberOfZones = zones.size();
info.NumberOfWindows = 0; info.NumberOfWindows = 0;
for (int i = 0; i < static_cast<int>(zones.size()); i++) for (int i = 0; i < static_cast<int>(zones.size()); i++)
{ {
if (!set->IsZoneEmpty(i)) if (!layoutWindows->IsZoneEmpty(i))
{ {
info.NumberOfWindows++; info.NumberOfWindows++;
} }
@@ -108,11 +109,6 @@ ZoneSetInfo GetZoneSetInfo(_In_opt_ IZoneSet* set) noexcept
return info; return info;
} }
ZoneSetInfo GetZoneSetInfo(_In_opt_ winrt::com_ptr<IZoneSet> set) noexcept
{
return GetZoneSetInfo(set.get());
}
void Trace::RegisterProvider() noexcept void Trace::RegisterProvider() noexcept
{ {
TraceLoggingRegister(g_hProvider); TraceLoggingRegister(g_hProvider);
@@ -263,28 +259,28 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept
TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); 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( TraceLoggingWrite(
g_hProvider, g_hProvider,
EventSnapNewWindowIntoZone, EventSnapNewWindowIntoZone,
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(reinterpret_cast<void*>(activeSet), ActiveSetKey), TraceLoggingValue(reinterpret_cast<void*>(activeLayout), ActiveSetKey),
TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey),
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); 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( TraceLoggingWrite(
g_hProvider, g_hProvider,
EventKeyboardSnapWindowToZone, EventKeyboardSnapWindowToZone,
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(reinterpret_cast<void*>(activeSet), ActiveSetKey), TraceLoggingValue(reinterpret_cast<void*>(activeLayout), ActiveSetKey),
TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey),
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
} }
@@ -361,41 +357,41 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept
TraceLoggingValue(wParam, KeyboardValueKey)); TraceLoggingValue(wParam, KeyboardValueKey));
} }
void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ winrt::com_ptr<IZoneSet> 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( TraceLoggingWrite(
g_hProvider, g_hProvider,
EventMoveOrResizeStartedKey, EventMoveOrResizeStartedKey,
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(reinterpret_cast<void*>(activeSet.get()), ActiveSetKey), TraceLoggingValue(reinterpret_cast<void*>(activeLayout), ActiveSetKey),
TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey),
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
} }
void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ winrt::com_ptr<IZoneSet> 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( TraceLoggingWrite(
g_hProvider, g_hProvider,
EventMoveOrResizeEndedKey, EventMoveOrResizeEndedKey,
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(reinterpret_cast<void*>(activeSet.get()), ActiveSetKey), TraceLoggingValue(reinterpret_cast<void*>(activeLayout), ActiveSetKey),
TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey),
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
} }
void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ winrt::com_ptr<IZoneSet> 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( TraceLoggingWrite(
g_hProvider, g_hProvider,
EventCycleActiveZoneSetKey, EventCycleActiveZoneSetKey,
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(reinterpret_cast<void*>(activeSet.get()), ActiveSetKey), TraceLoggingValue(reinterpret_cast<void*>(activeLayout), ActiveSetKey),
TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey),
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey), TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey),
TraceLoggingValue(static_cast<int>(mode), InputModeKey)); TraceLoggingValue(static_cast<int>(mode), InputModeKey));

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
struct Settings; struct Settings;
interface IZoneSet; class Layout;
class LayoutAssignedWindows;
class Trace class Trace
{ {
@@ -18,8 +19,8 @@ public:
static void EditorLaunched(int value) noexcept; static void EditorLaunched(int value) noexcept;
static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
static void QuickLayoutSwitched(bool shortcutUsed) noexcept; static void QuickLayoutSwitched(bool shortcutUsed) noexcept;
static void SnapNewWindowIntoZone(IZoneSet* activeSet) noexcept; static void SnapNewWindowIntoZone(Layout* activeaLayout, LayoutAssignedWindows* layoutWindows) noexcept;
static void KeyboardSnapWindowToZone(IZoneSet* activeSet) noexcept; static void KeyboardSnapWindowToZone(Layout* activeaLayout, LayoutAssignedWindows* layoutWindows) noexcept;
}; };
static void SettingsTelemetry(const Settings& settings) noexcept; static void SettingsTelemetry(const Settings& settings) noexcept;
@@ -35,8 +36,8 @@ public:
}; };
static void KeyUp(WPARAM wparam) noexcept; static void KeyUp(WPARAM wparam) noexcept;
static void MoveOrResizeStarted(_In_opt_ winrt::com_ptr<IZoneSet> activeSet) noexcept; static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
static void MoveOrResizeEnd(_In_opt_ winrt::com_ptr<IZoneSet> activeSet) noexcept; static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
static void CycleActiveZoneSet(_In_opt_ winrt::com_ptr<IZoneSet> activeSet, InputMode mode) noexcept; static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept;
}; };
}; };

View File

@@ -418,7 +418,7 @@ namespace FancyZonesUnitTests
}; };
// test // test
Layout expectedLayout { LayoutData expectedLayout {
.uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus,
.showSpacing = true, .showSpacing = true,
@@ -470,7 +470,7 @@ namespace FancyZonesUnitTests
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
// test // test
Layout expectedLayout{ LayoutData expectedLayout{
.uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus,
.showSpacing = true, .showSpacing = true,
@@ -530,7 +530,7 @@ namespace FancyZonesUnitTests
.monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value()
}; };
AppliedLayouts::instance().ApplyLayout(id, Layout{}); AppliedLayouts::instance().ApplyLayout(id, LayoutData{});
// test // test
Assert::IsTrue(AppliedLayouts::instance().IsLayoutApplied(id)); 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" }, .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() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value()
}; };
AppliedLayouts::instance().ApplyLayout(id1, Layout{}); AppliedLayouts::instance().ApplyLayout(id1, LayoutData{});
// test // test
FancyZonesDataTypes::WorkAreaId id2{ FancyZonesDataTypes::WorkAreaId id2{

View File

@@ -59,13 +59,13 @@ namespace FancyZonesUnitTests
// test // test
DefaultLayouts::instance().LoadData(); DefaultLayouts::instance().LoadData();
Layout horizontal{ LayoutData horizontal{
.uuid = FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value(), .uuid = FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Custom .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom
}; };
Assert::IsTrue(horizontal == DefaultLayouts::instance().GetDefaultLayout(MonitorConfiguraionType::Horizontal)); Assert::IsTrue(horizontal == DefaultLayouts::instance().GetDefaultLayout(MonitorConfiguraionType::Horizontal));
Layout vertical{ LayoutData vertical{
.uuid = GUID_NULL, .uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid,
.showSpacing = true, .showSpacing = true,
@@ -87,7 +87,7 @@ namespace FancyZonesUnitTests
// test // test
DefaultLayouts::instance().LoadData(); DefaultLayouts::instance().LoadData();
Layout priorityGrid{ LayoutData priorityGrid{
.uuid = GUID_NULL, .uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid, .type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid,
.showSpacing = DefaultValues::ShowSpacing, .showSpacing = DefaultValues::ShowSpacing,
@@ -105,7 +105,7 @@ namespace FancyZonesUnitTests
// test // test
DefaultLayouts::instance().LoadData(); DefaultLayouts::instance().LoadData();
Layout priorityGrid{ LayoutData priorityGrid{
.uuid = GUID_NULL, .uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid, .type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid,
.showSpacing = DefaultValues::ShowSpacing, .showSpacing = DefaultValues::ShowSpacing,

View File

@@ -0,0 +1,487 @@
#include "pch.h"
#include <filesystem>
#include <FancyZonesLib/FancyZonesData/LayoutDefaults.h>
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/ZoneIndexSetBitmask.h>
#include <FancyZonesLib/Layout.h>
#include <FancyZonesLib/Settings.h>
#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<Layout> m_layout{};
TEST_METHOD_INITIALIZE(Init)
{
m_layout = std::make_unique<Layout>(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<RECT>& 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<Layout>(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<Layout>(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<int>(OverlappingZonesAlgorithm::Smallest)));
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
auto layout = std::make_unique<Layout>(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<int>(OverlappingZonesAlgorithm::Smallest)));
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
auto layout = std::make_unique<Layout>(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<Layout> m_layout{};
HMONITOR m_monitor{};
const std::array<RECT, 9> 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>& 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<Layout>(m_data);
}
public:
TEST_METHOD (ValidValues)
{
const int zoneCount = 10;
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.spacing = 10;
data.zoneCount = zoneCount;
for (const auto& rect : m_workAreaRects)
{
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(rect, Mocks::Monitor());
Assert::IsTrue(result);
checkZones(layout, static_cast<ZoneSetLayoutType>(type), zoneCount, rect);
}
}
}
TEST_METHOD (InvalidMonitorInfo)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.spacing = 10;
data.zoneCount = 10;
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(RECT{0,0,0,0}, Mocks::Monitor());
Assert::IsFalse(result);
}
}
TEST_METHOD (ZeroSpacing)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.spacing = 0;
data.zoneCount = 10;
for (const auto& rect : m_workAreaRects)
{
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(rect, Mocks::Monitor());
Assert::IsTrue(result);
checkZones(layout, static_cast<ZoneSetLayoutType>(type), data.zoneCount, rect);
}
}
}
TEST_METHOD (LargeNegativeSpacing)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.zoneCount = 10;
data.spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1;
auto layout = std::make_unique<Layout>(data);
for (const auto& rect : m_workAreaRects)
{
auto result = layout->Init(rect, Mocks::Monitor());
if (type == static_cast<int>(ZoneSetLayoutType::Focus))
{
//Focus doesn't depends on spacing
Assert::IsTrue(result);
}
else
{
Assert::IsFalse(result);
}
}
}
}
TEST_METHOD (HorizontallyBigSpacing)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.zoneCount = 10;
for (const auto& rect : m_workAreaRects)
{
data.spacing = rect.right;
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(rect, Mocks::Monitor());
if (type == static_cast<int>(ZoneSetLayoutType::Focus))
{
//Focus doesn't depend on spacing
Assert::IsTrue(result);
}
else
{
Assert::IsFalse(result);
}
}
}
}
TEST_METHOD (VerticallyBigSpacing)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.zoneCount = 10;
for (const auto& rect : m_workAreaRects)
{
data.spacing = rect.bottom;
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(rect, Mocks::Monitor());
if (type == static_cast<int>(ZoneSetLayoutType::Focus))
{
//Focus doesn't depend on spacing
Assert::IsTrue(result);
}
else
{
Assert::IsFalse(result);
}
}
}
}
TEST_METHOD (ZeroZoneCount)
{
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.zoneCount = 0;
auto layout = std::make_unique<Layout>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
LayoutData data = m_data;
data.type = static_cast<ZoneSetLayoutType>(type);
data.zoneCount = zoneCount;
data.spacing = 0;
for (const auto& rect : m_workAreaRects)
{
auto layout = std::make_unique<Layout>(data);
auto result = layout->Init(rect, Mocks::Monitor());
Assert::IsTrue(result);
checkZones(layout, static_cast<ZoneSetLayoutType>(type), zoneCount, rect);
}
}
}
};
TEST_CLASS (ZoneIndexSetUnitTests)
{
TEST_METHOD (BitmaskFromIndexSetTest)
{
// prepare
ZoneIndexSet set{ 0, 64 };
// test
ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set);
Assert::AreEqual(static_cast<uint64_t>(1), bitmask.part1);
Assert::AreEqual(static_cast<uint64_t>(1), bitmask.part2);
}
TEST_METHOD (BitmaskToIndexSet)
{
// prepare
ZoneIndexSetBitmask bitmask{
.part1 = 1,
.part2 = 1,
};
// test
ZoneIndexSet set = bitmask.ToIndexSet();
Assert::AreEqual(static_cast<size_t>(2), set.size());
Assert::AreEqual(static_cast<ZoneIndex>(0), set[0]);
Assert::AreEqual(static_cast<ZoneIndex>(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]);
}
}
};
}

View File

@@ -0,0 +1,97 @@
#include "pch.h"
#include <filesystem>
#include <FancyZonesLib/FancyZonesData/LayoutDefaults.h>
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/ZoneIndexSetBitmask.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/util.h>
#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<ZoneIndex>{} == actual);
}
TEST_METHOD (ZoneIndexFromWindowNull)
{
LayoutAssignedWindows layoutWindows{};
layoutWindows.Assign(Mocks::Window(), { 0 });
auto actual = layoutWindows.GetZoneIndexSetFromWindow(nullptr);
Assert::IsTrue(std::vector<ZoneIndex>{} == actual);
}
TEST_METHOD (Assign)
{
HWND window = Mocks::Window();
LayoutAssignedWindows layoutWindows{};
layoutWindows.Assign(window, { 1, 2, 3 });
Assert::IsTrue(std::vector<ZoneIndex>{ 1, 2, 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (AssignEmpty)
{
HWND window = Mocks::Window();
LayoutAssignedWindows layoutWindows{};
layoutWindows.Assign(window, {});
Assert::IsTrue(std::vector<ZoneIndex>{} == layoutWindows.GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (AssignSeveralTimesSameWindow)
{
LayoutAssignedWindows layoutWindows{};
HWND window = Mocks::Window();
layoutWindows.Assign(window, { 0 });
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
layoutWindows.Assign(window, { 1 });
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
layoutWindows.Assign(window, { 2 });
Assert::IsTrue(std::vector<ZoneIndex>{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (DismissWindow)
{
LayoutAssignedWindows layoutWindows{};
HWND window = Mocks::Window();
layoutWindows.Assign(window, { 0 });
layoutWindows.Dismiss(window);
Assert::IsTrue(std::vector<ZoneIndex>{} == 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));
}
};
}

View File

@@ -47,8 +47,10 @@
<ClCompile Include="DefaultLayoutsTests.Spec.cpp" /> <ClCompile Include="DefaultLayoutsTests.Spec.cpp" />
<ClCompile Include="FancyZonesSettings.Spec.cpp" /> <ClCompile Include="FancyZonesSettings.Spec.cpp" />
<ClCompile Include="JsonHelpers.Tests.cpp" /> <ClCompile Include="JsonHelpers.Tests.cpp" />
<ClCompile Include="Layout.Spec.cpp" />
<ClCompile Include="LayoutHotkeysTests.Spec.cpp" /> <ClCompile Include="LayoutHotkeysTests.Spec.cpp" />
<ClCompile Include="LayoutTemplatesTests.Spec.cpp" /> <ClCompile Include="LayoutTemplatesTests.Spec.cpp" />
<ClCompile Include="LayoutAssignedWindows.Spec.cpp" />
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
@@ -57,7 +59,6 @@
<ClCompile Include="WorkArea.Spec.cpp" /> <ClCompile Include="WorkArea.Spec.cpp" />
<ClCompile Include="WorkAreaIdTests.Spec.cpp" /> <ClCompile Include="WorkAreaIdTests.Spec.cpp" />
<ClCompile Include="Zone.Spec.cpp" /> <ClCompile Include="Zone.Spec.cpp" />
<ClCompile Include="ZoneSet.Spec.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />

View File

@@ -18,9 +18,6 @@
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ZoneSet.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Zone.Spec.cpp"> <ClCompile Include="Zone.Spec.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@@ -57,6 +54,12 @@
<ClCompile Include="WorkAreaIdTests.Spec.cpp"> <ClCompile Include="WorkAreaIdTests.Spec.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Layout.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LayoutAssignedWindows.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DefaultLayoutsTests.Spec.cpp"> <ClCompile Include="DefaultLayoutsTests.Spec.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>

View File

@@ -6,6 +6,7 @@
#include <FancyZonesLib/FancyZonesData/AppliedLayouts.h> #include <FancyZonesLib/FancyZonesData/AppliedLayouts.h>
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h> #include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesData/DefaultLayouts.h> #include <FancyZonesLib/FancyZonesData/DefaultLayouts.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
#include "Util.h" #include "Util.h"
#include <common/utils/process_path.h> #include <common/utils/process_path.h>
@@ -53,10 +54,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr); Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId()); Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() }; const auto& layout = workArea->GetLayout();
Assert::IsNotNull(zoneSet); Assert::IsNotNull(layout.get());
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(zoneSet->LayoutType())); Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast<size_t>(defaultLayout.zoneCount), zoneSet->GetZones().size()); Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast<int>(layout->Zones().size()));
} }
TEST_METHOD (CreateCombinedWorkArea) TEST_METHOD (CreateCombinedWorkArea)
@@ -67,10 +69,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr); Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId()); Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() }; const auto& layout = workArea->GetLayout();
Assert::IsNotNull(zoneSet); Assert::IsNotNull(layout.get());
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(zoneSet->LayoutType())); Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast<size_t>(defaultLayout.zoneCount), zoneSet->GetZones().size()); Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast<int>(layout->Zones().size()));
} }
TEST_METHOD (CreateWorkAreaClonedFromParent) TEST_METHOD (CreateWorkAreaClonedFromParent)
@@ -83,7 +86,7 @@ namespace FancyZonesUnitTests
parentUniqueId.monitorId.serialNumber = L"serial-number"; parentUniqueId.monitorId.serialNumber = L"serial-number";
parentUniqueId.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value(); 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(), .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
.type = ZoneSetLayoutType::Rows, .type = ZoneSetLayoutType::Rows,
.showSpacing = true, .showSpacing = true,
@@ -96,10 +99,11 @@ namespace FancyZonesUnitTests
AppliedLayouts::instance().ApplyLayout(parentUniqueId, layout); AppliedLayouts::instance().ApplyLayout(parentUniqueId, layout);
auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, parentUniqueId); 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)); 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<int>(layout.type), static_cast<int>(actualLayout.type)); Assert::AreEqual(static_cast<int>(layout.type), static_cast<int>(actualLayout.type));
Assert::AreEqual(FancyZonesUtils::GuidToString(layout.uuid).value(), FancyZonesUtils::GuidToString(actualLayout.uuid).value()); Assert::AreEqual(FancyZonesUtils::GuidToString(layout.uuid).value(), FancyZonesUtils::GuidToString(actualLayout.uuid).value());
@@ -131,10 +135,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr); Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId()); Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() }; Assert::IsNotNull(workArea->GetLayout().get());
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Custom), static_cast<int>(zoneSet->LayoutType())); const auto& actualLayout = workArea->GetLayout();
Assert::IsTrue(FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value() == zoneSet->Id()); Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Custom), static_cast<int>(actualLayout->Type()));
Assert::IsTrue(FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value() == actualLayout->Id());
} }
TEST_METHOD (CreateWorkAreaWithTemplateDefault) TEST_METHOD (CreateWorkAreaWithTemplateDefault)
@@ -163,11 +168,12 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr); Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId()); Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() }; Assert::IsNotNull(workArea->GetLayout().get());
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast<int>(zoneSet->LayoutType())); const auto& actualLayout = workArea->GetLayout();
Assert::AreEqual(static_cast<size_t>(4), zoneSet->GetZones().size()); Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast<int>(actualLayout->Type()));
Assert::IsTrue(GUID_NULL == zoneSet->Id()); Assert::AreEqual(static_cast<size_t>(4), actualLayout->Zones().size());
Assert::IsTrue(GUID_NULL == actualLayout->Id());
} }
}; };
@@ -255,33 +261,17 @@ namespace FancyZonesUnitTests
const auto window = Mocks::Window(); const auto window = Mocks::Window();
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
workArea->MoveSizeUpdate({ 20, 20 }, true, true);
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = workArea->MoveSizeEnd(window, POINT{ 0, 0 }); const auto actual = workArea->MoveSizeEnd(window);
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
const auto zoneSet = workArea->ZoneSet(); const auto& layoutWindows = workArea->GetLayoutWindows();
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); const auto actualZoneIndexSet = layoutWindows->GetZoneIndexSetFromWindow(window);
const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window);
Assert::IsFalse(std::vector<ZoneIndex>{} == actualZoneIndexSet); Assert::IsFalse(std::vector<ZoneIndex>{} == 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<ZoneIndex>{} == actualZoneIndexSet);
}
TEST_METHOD (MoveSizeEndDifferentWindows) TEST_METHOD (MoveSizeEndDifferentWindows)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
@@ -290,7 +280,7 @@ namespace FancyZonesUnitTests
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
const auto expected = E_INVALIDARG; 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); Assert::AreEqual(expected, actual);
} }
@@ -300,74 +290,14 @@ namespace FancyZonesUnitTests
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
const auto expected = E_INVALIDARG; 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); 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<ZoneIndex>{} == 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<ZoneIndex>{ 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<ZoneIndex>{ 2 } == appHistoryArray[0].zoneIndexSet);
}
TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow) TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
workArea->SaveWindowProcessToZoneIndex(nullptr); workArea->SaveWindowProcessToZoneIndex(nullptr);
@@ -378,10 +308,9 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded) TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); 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); workArea->SaveWindowProcessToZoneIndex(window);
@@ -392,22 +321,21 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory) TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
const auto window = Mocks::WindowCreate(m_hInst); const auto window = Mocks::WindowCreate(m_hInst);
const auto processPath = get_process_path(window); const auto processPath = get_process_path(window);
const auto deviceId = workArea->UniqueId(); const auto deviceId = workArea->UniqueId();
const auto zoneSetId = workArea->ZoneSet()->Id(); const auto& layoutId = workArea->GetLayout()->Id();
// fill app zone history map // 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()); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
const auto& appHistoryArray1 = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath); const auto& appHistoryArray1 = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath);
Assert::AreEqual((size_t)1, appHistoryArray1.size()); Assert::AreEqual((size_t)1, appHistoryArray1.size());
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == appHistoryArray1[0].zoneIndexSet); Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == appHistoryArray1[0].zoneIndexSet);
// add zone without window // 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); workArea->SaveWindowProcessToZoneIndex(window);
Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size()); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
@@ -419,18 +347,16 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded) TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); auto window = Mocks::WindowCreate(m_hInst);
const auto processPath = get_process_path(window); const auto processPath = get_process_path(window);
const auto deviceId = workArea->UniqueId(); 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); workArea->MoveWindowIntoZoneByIndex(window, 0);
//fill app zone history map //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()); Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
const auto& appHistoryArray = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath); const auto& appHistoryArray = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath);
Assert::AreEqual((size_t)1, appHistoryArray.size()); Assert::AreEqual((size_t)1, appHistoryArray.size());
@@ -440,7 +366,7 @@ namespace FancyZonesUnitTests
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); 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; const auto& actual = appHistoryArray[0].zoneIndexSet;
Assert::IsTrue(expected == actual); Assert::IsTrue(expected == actual);
} }
@@ -448,8 +374,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); auto window = Mocks::WindowCreate(m_hInst);
int originalWidth = 450; int originalWidth = 450;
@@ -458,7 +383,7 @@ namespace FancyZonesUnitTests
SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW); SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW);
SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX); 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); workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
RECT inZoneRect; RECT inZoneRect;
@@ -467,4 +392,425 @@ namespace FancyZonesUnitTests
Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top); 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));
}
};
} }

View File

@@ -22,25 +22,38 @@ namespace FancyZonesUnitTests
public: public:
TEST_METHOD(TestCreateZone) TEST_METHOD(TestCreateZone)
{ {
winrt::com_ptr<IZone> zone = MakeZone(m_zoneRect, 1); Zone zone(m_zoneRect, 1);
Assert::IsNotNull(&zone); Assert::IsTrue(zone.IsValid());
CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect()); CustomAssert::AreEqual(m_zoneRect, zone.GetZoneRect());
} }
TEST_METHOD(TestCreateZoneZeroRect) TEST_METHOD(TestCreateZoneZeroRect)
{ {
RECT zoneRect{ 0, 0, 0, 0 }; RECT zoneRect{ 0, 0, 0, 0 };
winrt::com_ptr<IZone> zone = MakeZone(zoneRect, 1); Zone zone(zoneRect, 1);
Assert::IsNotNull(&zone); Assert::IsTrue(zone.IsValid());
CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); CustomAssert::AreEqual(zoneRect, zone.GetZoneRect());
} }
TEST_METHOD(GetSetId) TEST_METHOD(GetSetId)
{ {
constexpr ZoneIndex zoneId = 123; constexpr ZoneIndex zoneId = 123;
winrt::com_ptr<IZone> 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());
} }
}; };
} }

View File

@@ -1,851 +0,0 @@
#include "pch.h"
#include <filesystem>
#include <FancyZonesLib/FancyZonesData/LayoutDefaults.h>
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include "FancyZonesLib\ZoneIndexSetBitmask.h"
#include "FancyZonesLib\ZoneSet.h"
#include <FancyZonesLib/util.h>
#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<IZoneSet> 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<IZone>& expected, const winrt::com_ptr<IZone>& 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<RECT>& 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<IZoneSet> 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<IZone> zone = MakeZone({ 0, 0, 0, 0 }, 1);
Assert::IsNotNull(zone.get());
}
TEST_METHOD (MakeZoneFromInvalidRectWidth)
{
winrt::com_ptr<IZone> zone = MakeZone({ 100, 100, 99, 101 }, 1);
Assert::IsNull(zone.get());
}
TEST_METHOD (MakeZoneFromInvalidRectHeight)
{
winrt::com_ptr<IZone> 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<IZone> 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<ZoneIndex>{} == 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<ZoneIndex>{} == 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<ZoneIndex>{ 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<ZoneIndex>{} == 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<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1);
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2);
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0);
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0);
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{} == 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 1 } == set->GetZoneIndexSetFromWindow(window));
}
};
// MoveWindowIntoZoneByDirectionAndIndex is complicated enough to warrant it's own test class
TEST_CLASS (ZoneSetsMoveWindowIntoZoneByDirectionUnitTests)
{
winrt::com_ptr<IZoneSet> m_set;
winrt::com_ptr<IZone> m_zone1;
winrt::com_ptr<IZone> m_zone2;
winrt::com_ptr<IZone> 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 2 } == m_set->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveRightWithSameWindowAdded)
{
HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1 });
Assert::IsTrue(std::vector<ZoneIndex>{ 0, 1 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window2));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window2));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 2 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveLeftWithSameWindowAdded)
{
HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 1, 2 });
Assert::IsTrue(std::vector<ZoneIndex>{ 1, 2 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 2 } == m_set->GetZoneIndexSetFromWindow(window2));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window2));
m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true);
Assert::IsTrue(std::vector<ZoneIndex>{ 1 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 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<ZoneIndex>{ 0 } == m_set->GetZoneIndexSetFromWindow(window1));
Assert::IsTrue(std::vector<ZoneIndex>{ 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<IZoneSet> m_set;
HMONITOR m_monitor{};
const std::array<MONITORINFO, 9> 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<IZoneSet>& 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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<ZoneSetLayoutType>(type), zoneCount, monitorInfo);
}
}
}
TEST_METHOD (InvalidMonitorInfo)
{
const int spacing = 10;
const int zoneCount = 10;
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<ZoneSetLayoutType>(type), zoneCount, monitorInfo);
}
}
}
TEST_METHOD (LargeNegativeSpacing)
{
const int spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1;
const int zoneCount = 10;
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<int>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<int>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<int>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast<ZoneSetLayoutType>(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<ZoneSetLayoutType>(type), zoneCount, monitorInfo);
}
}
}
};
TEST_CLASS(ZoneIndexSetUnitTests)
{
TEST_METHOD (BitmaskFromIndexSetTest)
{
// prepare
ZoneIndexSet set {0, 64};
// test
ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(set);
Assert::AreEqual(static_cast<uint64_t>(1), bitmask.part1);
Assert::AreEqual(static_cast<uint64_t>(1), bitmask.part2);
}
TEST_METHOD(BitmaskToIndexSet)
{
// prepare
ZoneIndexSetBitmask bitmask{
.part1 = 1,
.part2 = 1,
};
// test
ZoneIndexSet set = bitmask.ToIndexSet();
Assert::AreEqual(static_cast<size_t>(2), set.size());
Assert::AreEqual(static_cast<ZoneIndex>(0), set[0]);
Assert::AreEqual(static_cast<ZoneIndex>(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]);
}
}
};
}