Skip to content

Commit

Permalink
[FancyZones] Improve zone selection (#9447)
Browse files Browse the repository at this point in the history
* Improve zone selection algorithm

* Thanks, spell checker

* Fix failing test case

* Add error logging

* Added support for different zone selection algorithms

* Revert "Fix failing test case"

This reverts commit 9f31a8a.
  • Loading branch information
ivan100sic committed Feb 8, 2021
1 parent 1899d01 commit 47e288d
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 21 deletions.
107 changes: 86 additions & 21 deletions src/modules/fancyzones/lib/ZoneSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Zone.h"
#include "util.h"

#include <common/logger/logger.h>
#include <common/display/dpi_aware.h>

#include <limits>
Expand Down Expand Up @@ -158,6 +159,11 @@ struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>
bool CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept;
bool CalculateCustomLayout(Rect workArea, int spacing) noexcept;
bool CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutInfo gridLayoutInfo, int spacing);
std::vector<size_t> ZoneSelectSubregion(const std::vector<size_t>& capturedZones, POINT pt) const;

// `compare` should return true if the first argument is a better choice than the second argument.
template<class CompareF>
std::vector<size_t> ZoneSelectPriority(const std::vector<size_t>& capturedZones, CompareF compare) const;

ZonesMap m_zones;
std::map<HWND, std::vector<size_t>> m_windowIndexSet;
Expand Down Expand Up @@ -211,7 +217,7 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept
}

// If captured zones do not overlap, return all of them
// Otherwise, return the smallest one
// Otherwise, return one of them based on the chosen selection algorithm.
bool overlap = false;
for (size_t i = 0; i < capturedZones.size(); ++i)
{
Expand Down Expand Up @@ -244,30 +250,28 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept

if (overlap)
{
size_t smallestIdx = 0;
for (size_t i = 1; i < capturedZones.size(); ++i)
{
RECT rectS;
RECT rectI;
try
{
rectS = m_zones.at(capturedZones[smallestIdx])->GetZoneRect();
rectI = m_zones.at(capturedZones[i])->GetZoneRect();
}
catch (std::out_of_range)
{
return {};
}
int smallestSize = (rectS.bottom - rectS.top) * (rectS.right - rectS.left);
int iSize = (rectI.bottom - rectI.top) * (rectI.right - rectI.left);
auto zoneArea = [](auto zone) {
RECT rect = zone->GetZoneRect();
return max(rect.bottom - rect.top, 0) * max(rect.right - rect.left, 0);
};

if (iSize <= smallestSize)
try
{
switch (m_config.SelectionAlgorithm)
{
smallestIdx = i;
case ZoneSelectionAlgorithm::SUBREGION:
return ZoneSelectSubregion(capturedZones, pt);
case ZoneSelectionAlgorithm::SMALLEST:
return ZoneSelectPriority(capturedZones, [&](auto zone1, auto zone2) { return zoneArea(zone1) < zoneArea(zone2); });
case ZoneSelectionAlgorithm::LARGEST:
return ZoneSelectPriority(capturedZones, [&](auto zone1, auto zone2) { return zoneArea(zone1) > zoneArea(zone2); });
}
}

capturedZones = { capturedZones[smallestIdx] };
catch (std::out_of_range)
{
Logger::error("Exception out_of_range was thrown in ZoneSet::ZonesFromPoint");
return { capturedZones[0] };
}
}

return capturedZones;
Expand Down Expand Up @@ -937,6 +941,67 @@ std::vector<size_t> ZoneSet::GetCombinedZoneRange(const std::vector<size_t>& ini
return result;
}

std::vector<size_t> ZoneSet::ZoneSelectSubregion(const std::vector<size_t>& 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;
size_t zoneIndex;

if (verticalSplit)
{
zoneIndex = (pt.y - overlap.top) * capturedZones.size() / height;
}
else
{
zoneIndex = (pt.x - overlap.left) * capturedZones.size() / width;
}

zoneIndex = std::clamp(zoneIndex, size_t(0), capturedZones.size() - 1);

return { capturedZones[zoneIndex] };
}

template<class CompareF>
std::vector<size_t> ZoneSet::ZoneSelectPriority(const std::vector<size_t>& 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);
Expand Down
9 changes: 9 additions & 0 deletions src/modules/fancyzones/lib/ZoneSet.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "Zone.h"
#include "Settings.h"

namespace FancyZonesDataTypes
{
Expand Down Expand Up @@ -151,6 +152,13 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
IFACEMETHOD_(std::vector<size_t>, GetCombinedZoneRange)(const std::vector<size_t>& initialZones, const std::vector<size_t>& finalZones) const = 0;
};

enum struct ZoneSelectionAlgorithm
{
SMALLEST = 0,
LARGEST = 1,
SUBREGION = 2,
};

struct ZoneSetConfig
{
ZoneSetConfig(
Expand All @@ -169,6 +177,7 @@ struct ZoneSetConfig
FancyZonesDataTypes::ZoneSetLayoutType LayoutType{};
HMONITOR Monitor{};
int SensitivityRadius;
ZoneSelectionAlgorithm SelectionAlgorithm = ZoneSelectionAlgorithm::SMALLEST;
};

winrt::com_ptr<IZoneSet> MakeZoneSet(ZoneSetConfig const& config) noexcept;

0 comments on commit 47e288d

Please sign in to comment.