Skip to content

Commit

Permalink
Improved monitor ordering (#1)
Browse files Browse the repository at this point in the history
* Implemented improved monitor ordering v1

* Fixed some embarrassing bugs, added some tests

* Added one more test

* Extracted a value to a variable

* ASCII art in unit test comments describing monitor layouts

* Removed empty line for consistency

* Update comment to match the code

* Refactored tests, added tests for X,Y offsets
  • Loading branch information
ivan100sic authored Mar 24, 2020
1 parent 34b636e commit 8564b56
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 43 deletions.
34 changes: 15 additions & 19 deletions src/modules/fancyzones/lib/FancyZones.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZone

void OnEditorExitEvent() noexcept;

std::vector<HMONITOR> GetMonitorsSorted();
std::vector<std::pair<HMONITOR, RECT>> GetRawMonitorData() noexcept;
std::vector<HMONITOR> GetMonitorsSorted() noexcept;
bool MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle);

const HINSTANCE m_hinstance{};
Expand Down Expand Up @@ -1140,7 +1141,18 @@ void FancyZones::OnEditorExitEvent() noexcept
JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData();
}

std::vector<HMONITOR> FancyZones::GetMonitorsSorted()
std::vector<HMONITOR> FancyZones::GetMonitorsSorted() noexcept
{
std::shared_lock readLock(m_lock);

auto monitorInfo = GetRawMonitorData();
OrderMonitors(monitorInfo);
std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
}

std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
{
std::shared_lock readLock(m_lock);

Expand All @@ -1155,23 +1167,7 @@ std::vector<HMONITOR> FancyZones::GetMonitorsSorted()
monitorInfo.push_back({ monitor, mi.rcMonitor });
}
}

auto isGreater = [](const std::pair<HMONITOR, RECT>& lhs, const std::pair<HMONITOR, RECT>& rhs) {
if (lhs.second.left > rhs.second.left)
{
return true;
}
else if (lhs.second.left == rhs.second.left)
{
return lhs.second.top > rhs.second.top;
}
return false;
};
std::sort(std::begin(monitorInfo), std::end(monitorInfo), isGreater);

std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
return monitorInfo;
}

bool FancyZones::MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle)
Expand Down
83 changes: 83 additions & 0 deletions src/modules/fancyzones/lib/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,86 @@ UINT GetDpiForMonitor(HMONITOR monitor) noexcept

return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}

void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
const size_t nMonitors = monitorInfo.size();
// blocking[i][j] - whether monitor i blocks monitor j in the ordering, i.e. monitor i should go before monitor j
std::vector<std::vector<bool>> blocking(nMonitors, std::vector<bool>(nMonitors, false));

// blockingCount[j] - the number of monitors which block monitor j
std::vector<size_t> blockingCount(nMonitors, 0);

for (size_t i = 0; i < nMonitors; i++)
{
RECT rectI = monitorInfo[i].second;
for (size_t j = 0; j < nMonitors; j++)
{
RECT rectJ = monitorInfo[j].second;
blocking[i][j] = rectI.top < rectJ.bottom && rectI.left < rectJ.right && i != j;
if (blocking[i][j])
{
blockingCount[j]++;
}
}
}

// used[i] - whether the sorting algorithm has used monitor i so far
std::vector<bool> used(nMonitors, false);

// the sorted sequence of monitors
std::vector<std::pair<HMONITOR, RECT>> sortedMonitorInfo;

for (size_t iteration = 0; iteration < nMonitors; iteration++)
{
// Indices of candidates to become the next monitor in the sequence
std::vector<size_t> candidates;

// First, find indices of all unblocked monitors
for (size_t i = 0; i < nMonitors; i++)
{
if (blockingCount[i] == 0 && !used[i])
{
candidates.push_back(i);
}
}

// In the unlikely event that there are no unblocked monitors, declare all unused monitors as candidates.
if (candidates.empty())
{
for (size_t i = 0; i < nMonitors; i++)
{
if (!used[i])
{
candidates.push_back(i);
}
}
}

// Pick the lexicographically smallest monitor as the next one
size_t smallest = candidates[0];
for (size_t j = 1; j < candidates.size(); j++)
{
size_t current = candidates[j];

// Compare (top, left) lexicographically
if (std::tie(monitorInfo[current].second.top, monitorInfo[current].second.left)
< std::tie(monitorInfo[smallest].second.top, monitorInfo[smallest].second.left))
{
smallest = current;
}
}

used[smallest] = true;
sortedMonitorInfo.push_back(monitorInfo[smallest]);
for (size_t i = 0; i < nMonitors; i++)
{
if (blocking[smallest][i])
{
blockingCount[i]--;
}
}
}

monitorInfo = std::move(sortedMonitorInfo);
}
3 changes: 2 additions & 1 deletion src/modules/fancyzones/lib/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,5 @@ inline unsigned char OpacitySettingToAlpha(int opacity)
return static_cast<unsigned char>(opacity * 2.55);
}

UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
Loading

0 comments on commit 8564b56

Please sign in to comment.