[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 коммит произвёл GitHub
Родитель 6431ccd370
Коммит ff290eef9d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
43 изменённых файлов: 2194 добавлений и 2242 удалений

Просмотреть файл

@ -25,7 +25,6 @@
#include <FancyZonesLib/MonitorUtils.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/ZoneSet.h>
#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/WindowMoveHandler.h>
#include <FancyZonesLib/WindowUtils.h>
@ -102,9 +101,9 @@ public:
m_windowMoveHandler.MoveSizeUpdate(monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()));
}
void MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept
void MoveSizeEnd(HWND window) noexcept
{
m_windowMoveHandler.MoveSizeEnd(window, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()));
m_windowMoveHandler.MoveSizeEnd(window, m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId()));
}
IFACEMETHODIMP_(void)
@ -155,7 +154,7 @@ protected:
private:
void UpdateWorkAreas() noexcept;
void CycleTabs(bool reverse) noexcept;
void CycleWindows(bool reverse) noexcept;
bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept;
@ -342,7 +341,7 @@ void FancyZones::MoveWindowIntoZone(HWND window, std::shared_ptr<WorkArea> workA
{
if (workArea)
{
Trace::FancyZones::SnapNewWindowIntoZone(workArea->ZoneSet());
Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea);
AppZoneHistory::instance().UpdateProcessIdToHandleMap(window, workArea->UniqueId());
@ -434,7 +433,14 @@ void FancyZones::WindowCreated(HWND window) noexcept
// Open on active monitor if window wasn't zoned
if (openOnActiveMonitor && !movedToAppLastZone)
{
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { MonitorUtils::OpenWindowOnActiveMonitor(window, active); } }).wait();
// window is recreated after switching virtual desktop
// avoid moving already opened windows after switching vd
bool isMoved = FancyZonesWindowProperties::RetreiveMovedOnOpeningProperty(window);
if (!isMoved)
{
FancyZonesWindowProperties::StampMovedOnOpeningProperty(window);
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { MonitorUtils::OpenWindowOnActiveMonitor(window, active); } }).wait();
}
}
}
@ -561,7 +567,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (wparam == static_cast<WPARAM>(HotkeyId::NextTab) || wparam == static_cast<WPARAM>(HotkeyId::PrevTab))
{
bool reverse = wparam == static_cast<WPARAM>(HotkeyId::PrevTab);
CycleTabs(reverse);
CycleWindows(reverse);
}
}
break;
@ -630,7 +636,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (message == WM_PRIV_MOVESIZEEND)
{
auto hwnd = reinterpret_cast<HWND>(wparam);
MoveSizeEnd(hwnd, ptScreen);
MoveSizeEnd(hwnd);
}
else if (message == WM_PRIV_LOCATIONCHANGE)
{
@ -708,13 +714,10 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
UpdateWorkAreas();
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
if (FancyZonesSettings::settings().displayChange_moveWindows)
{
if (FancyZonesSettings::settings().displayChange_moveWindows)
{
auto activeWorkAreas = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
m_windowMoveHandler.UpdateWindowsPositions(activeWorkAreas);
}
auto activeWorkAreas = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
m_windowMoveHandler.UpdateWindowsPositions(activeWorkAreas);
}
}
@ -782,7 +785,7 @@ void FancyZones::UpdateWorkAreas() noexcept
}
}
void FancyZones::CycleTabs(bool reverse) noexcept
void FancyZones::CycleWindows(bool reverse) noexcept
{
auto window = GetForegroundWindow();
HMONITOR current = WorkAreaKeyFromWindow(window);
@ -790,7 +793,7 @@ void FancyZones::CycleTabs(bool reverse) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current);
if (workArea)
{
workArea->CycleTabs(window, reverse);
workArea->CycleWindows(window, reverse);
}
}
@ -808,7 +811,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), *currMonitorInfo);
if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea))
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true;
}
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
@ -844,7 +847,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
}
else
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return moved;
}
@ -853,7 +856,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, workArea);
if (moved)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return moved;
}
@ -894,13 +897,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor);
if (workArea)
{
auto zoneSet = workArea->ZoneSet();
if (zoneSet)
const auto& layout = workArea->GetLayout();
if (layout)
{
const auto zones = zoneSet->GetZones();
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone->GetZoneRect();
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left;
@ -929,7 +932,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea);
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true;
}
@ -942,13 +945,13 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), current);
if (workArea)
{
auto zoneSet = workArea->ZoneSet();
if (zoneSet)
const auto& layout = workArea->GetLayout();
if (layout)
{
const auto zones = zoneSet->GetZones();
const auto& zones = layout->Zones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone->GetZoneRect();
RECT zoneRect = zone.GetZoneRect();
zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left;
@ -974,7 +977,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
// Moving to another monitor succeeded
const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea);
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
return true;
}
else
@ -1013,7 +1016,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle
bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea);
if (result)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return result;
}
@ -1022,7 +1025,7 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle
bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea);
if (result)
{
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet());
Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
}
return result;
}
@ -1130,7 +1133,20 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
HMONITOR monitor = WorkAreaKeyFromWindow(window);
auto workArea = m_workAreaHandler.GetWorkArea(VirtualDesktop::instance().GetCurrentVirtualDesktopId(), monitor);
if (workArea && workArea->ZoneSet() && workArea->ZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank)
if (!workArea)
{
Logger::error(L"No work area for processing snap hotkey");
return false;
}
const auto& layout = workArea->GetLayout();
if (!layout)
{
Logger::error(L"No layout for processing snap hotkey");
return false;
}
if (layout->Type() != FancyZonesDataTypes::ZoneSetLayoutType::Blank)
{
if (vkCode == VK_UP || vkCode == VK_DOWN)
{
@ -1142,6 +1158,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
}
}
}
return false;
}
@ -1193,7 +1210,7 @@ std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
for (const auto& [monitor, workArea] : activeWorkAreaMap)
{
if (workArea->ZoneSet() != nullptr)
if (workArea->GetLayout() != nullptr)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);

Просмотреть файл

@ -556,7 +556,7 @@ void AppZoneHistory::UpdateProcessIdToHandleMap(HWND window, const FancyZonesDat
}
}
ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) const
ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const
{
auto processPath = get_process_path_waiting_uwp(window);
if (processPath.empty())

Просмотреть файл

@ -54,7 +54,7 @@ public:
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
void UpdateProcessIdToHandleMap(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId);
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) const;
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const;
void SyncVirtualDesktops();
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -5,7 +5,7 @@
#include <memory>
#include <optional>
#include <FancyZonesLib/FancyZonesData/Layout.h>
#include <FancyZonesLib/FancyZonesData/LayoutData.h>
#include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/GuidUtils.h>
#include <FancyZonesLib/ModuleConstants.h>
@ -64,7 +64,7 @@ public:
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;
const TCustomLayoutMap& GetAllLayouts() const noexcept;

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -5,7 +5,7 @@
#include <FancyZonesLib/FancyZonesData/LayoutDefaults.h>
#include <FancyZonesLib/FancyZonesDataTypes.h>
struct Layout
struct LayoutData
{
GUID uuid = GUID_NULL;
FancyZonesDataTypes::ZoneSetLayoutType type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid;
@ -15,7 +15,7 @@ struct Layout
int sensitivityRadius = DefaultValues::SensitivityRadius;
};
inline bool operator==(const Layout& lhs, const Layout& rhs)
inline bool operator==(const LayoutData& lhs, const LayoutData& rhs)
{
return lhs.uuid == rhs.uuid &&
lhs.type == rhs.type &&

Просмотреть файл

@ -10,11 +10,11 @@ namespace JsonUtils
{
struct TemplateLayoutJSON
{
static std::optional<Layout> FromJson(const json::JsonObject& json)
static std::optional<LayoutData> FromJson(const json::JsonObject& json)
{
try
{
Layout data;
LayoutData data;
data.uuid = GUID_NULL;
data.type = FancyZonesDataTypes::TypeFromString(std::wstring{ json.GetNamedString(NonLocalizable::LayoutTemplatesIds::TypeID) });
@ -32,9 +32,9 @@ namespace JsonUtils
}
};
std::vector<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);
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)
{

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -83,6 +83,17 @@ ZoneIndexSet FancyZonesWindowProperties::RetrieveZoneIndexProperty(HWND window)
return bitmask.ToIndexSet();
}
void FancyZonesWindowProperties::StampMovedOnOpeningProperty(HWND window)
{
::SetPropW(window, ZonedWindowProperties::PropertyMovedOnOpening, (HANDLE)1);
}
bool FancyZonesWindowProperties::RetreiveMovedOnOpeningProperty(HWND window)
{
HANDLE handle = ::GetProp(window, ZonedWindowProperties::PropertyMovedOnOpening);
return handle != nullptr;
}
std::optional<size_t> FancyZonesWindowProperties::GetTabSortKeyWithinZone(HWND window)
{
auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone);

Просмотреть файл

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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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{};
};

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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;
};

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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 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;
bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, std::shared_ptr<WorkArea> workArea) noexcept;

Просмотреть файл

@ -127,12 +127,17 @@ HRESULT WorkArea::MoveSizeEnter(HWND window) noexcept
m_highlightZone = {};
m_initialHighlightZone = {};
ShowZonesOverlay();
Trace::WorkArea::MoveOrResizeStarted(m_zoneSet);
Trace::WorkArea::MoveOrResizeStarted(m_layout.get(), m_layoutWindows.get());
return S_OK;
}
HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept
{
if (!m_layout)
{
return -1;
}
bool redraw = false;
POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_window, &ptClient, 1);
@ -150,7 +155,7 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s
}
else
{
highlightZone = m_zoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone);
highlightZone = m_layout->GetCombinedZoneRange(m_initialHighlightZone, highlightZone);
}
}
else
@ -167,33 +172,24 @@ HRESULT WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool s
redraw = true;
}
if (redraw)
if (redraw && m_zonesOverlay)
{
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
}
return S_OK;
}
HRESULT WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept
HRESULT WorkArea::MoveSizeEnd(HWND window) noexcept
{
if (m_windowMoveSize != window)
{
return E_INVALIDARG;
}
if (m_zoneSet)
{
POINT ptClient = ptScreen;
MapWindowPoints(nullptr, m_window, &ptClient, 1);
m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone);
MoveWindowIntoZoneByIndexSet(window, m_highlightZone);
if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{
SaveWindowProcessToZoneIndex(window);
}
}
Trace::WorkArea::MoveOrResizeEnd(m_zoneSet);
Trace::WorkArea::MoveOrResizeEnd(m_layout.get(), m_layoutWindows.get());
HideZonesOverlay();
m_windowMoveSize = nullptr;
@ -207,80 +203,264 @@ void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept
void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept
{
if (m_zoneSet)
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty())
{
m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet);
return;
}
FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
auto rect = m_layout->GetCombinedZonesRect(indexSet);
auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
m_layoutWindows->Assign(window, indexSet);
FancyZonesWindowProperties::StampZoneIndexProperty(window, indexSet);
SaveWindowProcessToZoneIndex(window);
}
bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept
{
if (m_zoneSet)
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
{
if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle))
return false;
}
auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window);
auto numZones = m_layout->Zones().size();
// The window was not assigned to any zone here
if (zoneIndexes.size() == 0)
{
MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
}
else
{
ZoneIndex oldId = zoneIndexes[0];
// We reached the edge
if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == numZones - 1))
{
if (!FancyZonesWindowUtils::HasVisibleOwner(window))
if (!cycle)
{
SaveWindowProcessToZoneIndex(window);
return false;
}
MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
}
else
{
// We didn't reach the edge
if (vkCode == VK_LEFT)
{
MoveWindowIntoZoneByIndex(window, oldId - 1);
}
else
{
MoveWindowIntoZoneByIndex(window, oldId + 1);
}
return true;
}
}
return false;
if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{
SaveWindowProcessToZoneIndex(window);
}
return true;
}
bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept
{
if (m_zoneSet)
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
{
if (m_zoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle))
return false;
}
const auto& zones = m_layout->Zones();
std::vector<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);
Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
return true;
}
}
return false;
}
bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept
{
if (m_zoneSet)
if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
{
if (m_zoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode))
return false;
}
RECT windowRect;
if (!GetWindowRect(window, &windowRect))
{
Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError()));
return false;
}
const auto& zones = m_layout->Zones();
auto appliedZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
const auto& extendModeData = m_layoutWindows->ExtendWindowData();
std::vector<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);
return true;
usedZoneIndices[idx] = true;
}
// Move to coordinates relative to windowZone
windowRect.top -= m_workAreaRect.top();
windowRect.bottom -= m_workAreaRect.top();
windowRect.left -= m_workAreaRect.left();
windowRect.right -= m_workAreaRect.left();
}
for (size_t i = 0; i < zones.size(); i++)
{
if (!usedZoneIndices[i])
{
zoneRects.emplace_back(zones.at(i).GetZoneRect());
freeZoneIndices.emplace_back(i);
}
}
auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
ZoneIndex targetZone = freeZoneIndices[result];
ZoneIndexSet resultIndexSet;
// First time with selectManyZones = true for this window?
if (finalIndexIt == extendModeData->windowFinalIndex.end())
{
// Already zoned?
if (appliedZones.size())
{
extendModeData->windowInitialIndexSet[window] = appliedZones;
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = m_layout->GetCombinedZoneRange(appliedZones, { targetZone });
}
else
{
extendModeData->windowInitialIndexSet[window] = { targetZone };
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = { targetZone };
}
}
else
{
auto deletethis = extendModeData->windowInitialIndexSet[window];
extendModeData->windowFinalIndex[window] = targetZone;
resultIndexSet = m_layout->GetCombinedZoneRange(extendModeData->windowInitialIndexSet[window], { targetZone });
}
auto rect = m_layout->GetCombinedZonesRect(resultIndexSet);
auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
m_layoutWindows->Extend(window, resultIndexSet);
FancyZonesWindowProperties::StampZoneIndexProperty(window, resultIndexSet);
SaveWindowProcessToZoneIndex(window);
return true;
}
return false;
}
void WorkArea::SaveWindowProcessToZoneIndex(HWND window) noexcept
{
if (m_zoneSet)
if (m_layout && m_layoutWindows)
{
auto zoneIndexSet = m_zoneSet->GetZoneIndexSetFromWindow(window);
auto zoneIndexSet = m_layoutWindows->GetZoneIndexSetFromWindow(window);
if (zoneIndexSet.size())
{
OLECHAR* guidString;
if (StringFromCLSID(m_zoneSet->Id(), &guidString) == S_OK)
auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (guidStr.has_value())
{
AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet);
AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zoneIndexSet);
}
CoTaskMemFree(guidString);
}
}
}
ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept
{
if (m_zoneSet)
if (m_layout)
{
wil::unique_cotaskmem_string zoneSetId;
if (SUCCEEDED(StringFromCLSID(m_zoneSet->Id(), &zoneSetId)))
auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
if (guidStr.has_value())
{
return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, zoneSetId.get());
return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value());
}
else
{
@ -297,10 +477,10 @@ ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const noexcept
void WorkArea::ShowZonesOverlay() noexcept
{
if (m_window)
if (m_window && m_layout)
{
SetAsTopmostWindow();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->Show();
}
}
@ -324,37 +504,37 @@ void WorkArea::UpdateActiveZoneSet() noexcept
AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId);
}
CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm);
if (m_window && m_zoneSet)
CalculateZoneSet();
if (m_window && m_layout)
{
m_highlightZone.clear();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
}
}
void WorkArea::CycleTabs(HWND window, bool reverse) noexcept
void WorkArea::CycleWindows(HWND window, bool reverse) noexcept
{
if (m_zoneSet)
if (m_layoutWindows)
{
m_zoneSet->CycleTabs(window, reverse);
m_layoutWindows->CycleWindows(window, reverse);
}
}
void WorkArea::ClearSelectedZones() noexcept
{
if (m_highlightZone.size())
if (m_highlightZone.size() && m_layout)
{
m_highlightZone.clear();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
}
}
void WorkArea::FlashZones() noexcept
{
if (m_window)
if (m_window && m_layout)
{
SetAsTopmostWindow();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->Flash();
}
}
@ -391,36 +571,25 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId)
}
}
CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm);
CalculateZoneSet();
}
void WorkArea::CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept
void WorkArea::CalculateZoneSet() noexcept
{
const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
if (!appliedLayout.has_value())
{
Logger::error(L"Layout wasn't applied. Can't init zone set");
Logger::error(L"Layout wasn't applied. Can't init layout on work area {}x{}", m_workAreaRect.width(), m_workAreaRect.height());
return;
}
auto zoneSet = MakeZoneSet(ZoneSetConfig(
appliedLayout->uuid,
appliedLayout->type,
m_monitor,
appliedLayout->sensitivityRadius,
overlappingAlgorithm));
m_layout = std::make_unique<Layout>(appliedLayout.value());
m_layout->Init(m_workAreaRect, m_monitor);
bool showSpacing = appliedLayout->showSpacing;
int spacing = showSpacing ? appliedLayout->spacing : 0;
int zoneCount = appliedLayout->zoneCount;
zoneSet->CalculateZones(m_workAreaRect, zoneCount, spacing);
UpdateActiveZoneSet(zoneSet.get());
}
void WorkArea::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept
{
m_zoneSet.copy_from(zoneSet);
if (!m_layoutWindows)
{
m_layoutWindows = std::make_unique<LayoutAssignedWindows>();
}
}
LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
@ -447,10 +616,11 @@ LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
ZoneIndexSet WorkArea::ZonesFromPoint(POINT pt) noexcept
{
if (m_zoneSet)
if (m_layout)
{
return m_zoneSet->ZonesFromPoint(pt);
return m_layout->ZonesFromPoint(pt);
}
return {};
}

Просмотреть файл

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

Просмотреть файл

@ -1,54 +1,45 @@
#include "pch.h"
#include <Shellscalingapi.h>
#include <common/display/dpi_aware.h>
#include <common/display/monitors.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;
}

Просмотреть файл

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

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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;

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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]);
}
}
};
}

Просмотреть файл

@ -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));
}
};
}

Просмотреть файл

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

Просмотреть файл

@ -18,9 +18,6 @@
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ZoneSet.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Zone.Spec.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -57,6 +54,12 @@
<ClCompile Include="WorkAreaIdTests.Spec.cpp">
<Filter>Source Files</Filter>
</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">
<Filter>Source Files</Filter>
</ClCompile>

Просмотреть файл

@ -6,6 +6,7 @@
#include <FancyZonesLib/FancyZonesData/AppliedLayouts.h>
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesData/DefaultLayouts.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
#include "Util.h"
#include <common/utils/process_path.h>
@ -53,10 +54,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() };
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(zoneSet->LayoutType()));
Assert::AreEqual(static_cast<size_t>(defaultLayout.zoneCount), zoneSet->GetZones().size());
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
Assert::IsNotNull(workArea->GetLayoutWindows().get());
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)
@ -67,10 +69,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() };
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(defaultLayout.type), static_cast<int>(zoneSet->LayoutType()));
Assert::AreEqual(static_cast<size_t>(defaultLayout.zoneCount), zoneSet->GetZones().size());
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
Assert::IsNotNull(workArea->GetLayoutWindows().get());
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)
@ -83,7 +86,7 @@ namespace FancyZonesUnitTests
parentUniqueId.monitorId.serialNumber = L"serial-number";
parentUniqueId.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value();
Layout layout{
LayoutData layout{
.uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
.type = ZoneSetLayoutType::Rows,
.showSpacing = true,
@ -96,10 +99,11 @@ namespace FancyZonesUnitTests
AppliedLayouts::instance().ApplyLayout(parentUniqueId, layout);
auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, parentUniqueId);
Assert::IsNotNull(actualWorkArea->ZoneSet());
Assert::IsNotNull(actualWorkArea->GetLayout().get());
Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get());
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_uniqueId));
auto actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_uniqueId);
const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_uniqueId);
Assert::AreEqual(static_cast<int>(layout.type), static_cast<int>(actualLayout.type));
Assert::AreEqual(FancyZonesUtils::GuidToString(layout.uuid).value(), FancyZonesUtils::GuidToString(actualLayout.uuid).value());
@ -131,10 +135,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() };
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Custom), static_cast<int>(zoneSet->LayoutType()));
Assert::IsTrue(FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value() == zoneSet->Id());
Assert::IsNotNull(workArea->GetLayout().get());
const auto& actualLayout = workArea->GetLayout();
Assert::AreEqual(static_cast<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)
@ -163,11 +168,12 @@ namespace FancyZonesUnitTests
Assert::IsFalse(workArea == nullptr);
Assert::IsTrue(m_uniqueId == workArea->UniqueId());
auto* zoneSet{ workArea->ZoneSet() };
Assert::IsNotNull(zoneSet);
Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast<int>(zoneSet->LayoutType()));
Assert::AreEqual(static_cast<size_t>(4), zoneSet->GetZones().size());
Assert::IsTrue(GUID_NULL == zoneSet->Id());
Assert::IsNotNull(workArea->GetLayout().get());
const auto& actualLayout = workArea->GetLayout();
Assert::AreEqual(static_cast<int>(FancyZonesDataTypes::ZoneSetLayoutType::Grid), static_cast<int>(actualLayout->Type()));
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();
workArea->MoveSizeEnter(window);
workArea->MoveSizeUpdate({ 20, 20 }, true, true);
const auto expected = S_OK;
const auto actual = workArea->MoveSizeEnd(window, POINT{ 0, 0 });
const auto actual = workArea->MoveSizeEnd(window);
Assert::AreEqual(expected, actual);
const auto zoneSet = workArea->ZoneSet();
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0);
const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window);
const auto& layoutWindows = workArea->GetLayoutWindows();
const auto actualZoneIndexSet = layoutWindows->GetZoneIndexSetFromWindow(window);
Assert::IsFalse(std::vector<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)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
@ -290,7 +280,7 @@ namespace FancyZonesUnitTests
workArea->MoveSizeEnter(window);
const auto expected = E_INVALIDARG;
const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
const auto actual = workArea->MoveSizeEnd(Mocks::Window());
Assert::AreEqual(expected, actual);
}
@ -300,74 +290,14 @@ namespace FancyZonesUnitTests
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
const auto expected = E_INVALIDARG;
const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
const auto actual = workArea->MoveSizeEnd(Mocks::Window());
Assert::AreEqual(expected, actual);
}
TEST_METHOD (MoveSizeEndInvalidPoint)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::Window();
workArea->MoveSizeEnter(window);
const auto expected = S_OK;
const auto actual = workArea->MoveSizeEnd(window, POINT{ -1, -1 });
Assert::AreEqual(expected, actual);
const auto zoneSet = workArea->ZoneSet();
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0);
const auto actualZoneIndex = zoneSet->GetZoneIndexSetFromWindow(window);
Assert::IsFalse(std::vector<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)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
workArea->SaveWindowProcessToZoneIndex(nullptr);
@ -378,10 +308,9 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst);
workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0);
workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
workArea->SaveWindowProcessToZoneIndex(window);
@ -392,22 +321,21 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
const auto window = Mocks::WindowCreate(m_hInst);
const auto processPath = get_process_path(window);
const auto deviceId = workArea->UniqueId();
const auto zoneSetId = workArea->ZoneSet()->Id();
const auto& layoutId = workArea->GetLayout()->Id();
// fill app zone history map
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(zoneSetId), { 0 }));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(layoutId), { 0 }));
Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
const auto& appHistoryArray1 = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath);
Assert::AreEqual((size_t)1, appHistoryArray1.size());
Assert::IsTrue(std::vector<ZoneIndex>{ 0 } == appHistoryArray1[0].zoneIndexSet);
// add zone without window
workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0);
workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
workArea->SaveWindowProcessToZoneIndex(window);
Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
@ -419,18 +347,16 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst);
const auto processPath = get_process_path(window);
const auto deviceId = workArea->UniqueId();
const auto zoneSetId = workArea->ZoneSet()->Id();
const auto& layoutId = workArea->GetLayout()->Id();
workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0);
workArea->MoveWindowIntoZoneByIndex(window, 0);
//fill app zone history map
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(zoneSetId), { 2 }));
Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, deviceId, Helpers::GuidToString(layoutId), { 2 }));
Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetFullAppZoneHistory().size());
const auto& appHistoryArray = AppZoneHistory::instance().GetFullAppZoneHistory().at(processPath);
Assert::AreEqual((size_t)1, appHistoryArray.size());
@ -440,7 +366,7 @@ namespace FancyZonesUnitTests
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& expected = workArea->ZoneSet()->GetZoneIndexSetFromWindow(window);
const auto& expected = workArea->GetLayoutWindows()->GetZoneIndexSetFromWindow(window);
const auto& actual = appHistoryArray[0].zoneIndexSet;
Assert::IsTrue(expected == actual);
}
@ -448,8 +374,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt)
{
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, m_parentUniqueId);
Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst);
int originalWidth = 450;
@ -458,7 +383,7 @@ namespace FancyZonesUnitTests
SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW);
SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX);
workArea->ZoneSet()->CalculateZones(RECT{ 0, 0, 1920, 1080 }, 1, 0);
workArea->GetLayout()->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
RECT inZoneRect;
@ -467,4 +392,425 @@ namespace FancyZonesUnitTests
Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top);
}
};
TEST_CLASS (WorkAreaMoveWindowUnitTests)
{
const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
const FancyZonesDataTypes::WorkAreaId m_uniqueId{
.monitorId = {
.monitor = Mocks::Monitor(),
.deviceId = {
.id = L"DELA026",
.instanceId = L"5&10a58c63&0&UID16777488",
.number = 1,
},
.serialNumber = L"serial-number"
},
.virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
};
FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty
HINSTANCE m_hInst{};
HMONITOR m_monitor{};
void PrepareEmptyLayout()
{
json::JsonObject root{};
json::JsonArray layoutsArray{};
{
json::JsonObject layout{};
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Blank)));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(0));
json::JsonObject workAreaId{};
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_uniqueId.monitorId.deviceId.id));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_uniqueId.monitorId.deviceId.instanceId));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_uniqueId.monitorId.serialNumber));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_uniqueId.monitorId.deviceId.number));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
json::JsonObject obj{};
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
layoutsArray.Append(obj);
}
root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
AppliedLayouts::instance().LoadData();
}
void PrepareGridLayout()
{
json::JsonObject root{};
json::JsonArray layoutsArray{};
{
json::JsonObject layout{};
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
json::JsonObject workAreaId{};
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_uniqueId.monitorId.deviceId.id));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_uniqueId.monitorId.deviceId.instanceId));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_uniqueId.monitorId.serialNumber));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_uniqueId.monitorId.deviceId.number));
workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
json::JsonObject obj{};
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
layoutsArray.Append(obj);
}
root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
AppliedLayouts::instance().LoadData();
}
TEST_METHOD_INITIALIZE(Init)
{
AppZoneHistory::instance().LoadData();
AppliedLayouts::instance().LoadData();
}
TEST_METHOD_CLEANUP(CleanUp)
{
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
}
TEST_METHOD (EmptyZonesMoveLeftByIndex)
{
// prepare
PrepareEmptyLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (EmptyZonesRightByIndex)
{
// prepare
PrepareEmptyLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveLeftNonAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveRightNonAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByIndex)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
const auto& layoutWindows = workArea->GetLayoutWindows();
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
}
TEST_METHOD (MoveAppliedWindowByIndexCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ (ZoneIndex)workArea->GetLayout()->Zones().size() - 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByIndexNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (EmptyZonesMoveByPosition)
{
// prepare
PrepareEmptyLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveLeftNonAppliedWindowByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveRightNonAppliedWindowByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowHorizontallyByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowVerticallyByPosition)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_DOWN, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionHorizontallyCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionHorizontallyNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionVerticallyCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
const auto& layoutWindows = workArea->GetLayoutWindows();
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, true);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (MoveAppliedWindowByPositionVerticallyNoCycle)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, false);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (ExtendZoneHorizontally)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->ExtendWindowByDirectionAndPosition(window, VK_RIGHT);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (ExtendZoneVertically)
{
// prepare
PrepareGridLayout();
auto workArea = MakeWorkArea(m_hInst, m_uniqueId.monitorId.monitor, m_uniqueId, m_parentUniqueId);
const auto window = Mocks::WindowCreate(m_hInst);
workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
// test
workArea->ExtendWindowByDirectionAndPosition(window, VK_DOWN);
const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
const auto& layoutWindows = workArea->GetLayoutWindows();
Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
};
}

Просмотреть файл

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

Просмотреть файл

@ -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]);
}
}
};
}