mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 11:16:51 +02:00
337 lines
11 KiB
C++
337 lines
11 KiB
C++
#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;
|
|
}
|
|
|
|
auto spacing = m_data.showSpacing ? m_data.spacing : 0;
|
|
|
|
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, spacing);
|
|
break;
|
|
case FancyZonesDataTypes::ZoneSetLayoutType::Rows:
|
|
m_zones = LayoutConfigurator::Rows(workArea, m_data.zoneCount, spacing);
|
|
break;
|
|
case FancyZonesDataTypes::ZoneSetLayoutType::Grid:
|
|
m_zones = LayoutConfigurator::Grid(workArea, m_data.zoneCount, spacing);
|
|
break;
|
|
case FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid:
|
|
m_zones = LayoutConfigurator::PriorityGrid(workArea, m_data.zoneCount, 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(), 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;
|
|
}
|