Skip to content

Commit

Permalink
Basic support for snapping to multiple zones (#1955)
Browse files Browse the repository at this point in the history
* Refactor a method which resizes windows

* Completed initial work for MultiZones

Without changing any test, they all pass!

* Implemented a basic version of Multizones, updated some tests

* Reduced the sensitivity radius

* Added a few must-have unit tests for Multizones

* Some fixups

* Took care of the conflict between this and #1938

* Improved how zones are detected, reverted a change in one unit test

* Resolved another merge conflict

* Fixed bugs related to stamping
  • Loading branch information
ivan100sic authored Apr 10, 2020
1 parent e896e1b commit 629ba76
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 126 deletions.
9 changes: 8 additions & 1 deletion src/modules/fancyzones/lib/FancyZones.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZone
void AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept;

void MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int index) noexcept;
void MoveWindowIntoZoneByIndexSet(HWND window, HMONITOR monitor, const std::vector<int>& indexSet) noexcept;

protected:
static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
Expand Down Expand Up @@ -695,6 +696,12 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
}

void FancyZones::MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int index) noexcept
{
std::shared_lock readLock(m_lock);
MoveWindowIntoZoneByIndexSet(window, monitor, { index });
}

void FancyZones::MoveWindowIntoZoneByIndexSet(HWND window, HMONITOR monitor, const std::vector<int>& indexSet) noexcept
{
std::shared_lock readLock(m_lock);
if (window != m_windowMoveSize)
Expand All @@ -706,7 +713,7 @@ void FancyZones::MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int in
if (zoneWindow != m_zoneWindowMap.end())
{
const auto& zoneWindowPtr = zoneWindow->second;
zoneWindowPtr->MoveWindowIntoZoneByIndex(window, index);
zoneWindowPtr->MoveWindowIntoZoneByIndexSet(window, indexSet);
}
}
}
Expand Down
35 changes: 7 additions & 28 deletions src/modules/fancyzones/lib/Zone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <common/monitors.h>
#include "Zone.h"
#include "Settings.h"
#include "util.h"

struct Zone : winrt::implements<Zone, IZone>
{
Expand All @@ -20,6 +21,7 @@ struct Zone : winrt::implements<Zone, IZone>
IFACEMETHODIMP_(void) RemoveWindowFromZone(HWND window, bool restoreSize) noexcept;
IFACEMETHODIMP_(void) SetId(size_t id) noexcept { m_id = id; }
IFACEMETHODIMP_(size_t) Id() noexcept { return m_id; }
IFACEMETHODIMP_(RECT) ComputeActualZoneRect(HWND window, HWND zoneWindow) noexcept;

private:
void SizeWindowToZone(HWND window, HWND zoneWindow) noexcept;
Expand Down Expand Up @@ -61,12 +63,11 @@ IFACEMETHODIMP_(void) Zone::RemoveWindowFromZone(HWND window, bool restoreSize)

void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
{
// Skip invisible windows
if (!IsWindowVisible(window))
{
return;
}
SizeWindowToRect(window, ComputeActualZoneRect(window, zoneWindow));
}

RECT Zone::ComputeActualZoneRect(HWND window, HWND zoneWindow) noexcept
{
// Take care of 1px border
RECT newWindowRect = m_zoneRect;

Expand Down Expand Up @@ -111,29 +112,7 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
}

WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);

//wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
for (int i = 0; i < 5 && (placement.showCmd & SW_SHOWMINIMIZED) != 0; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
::GetWindowPlacement(window, &placement);
}

// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd & SW_SHOWMINIMIZED) == 0)
{
placement.showCmd = SW_RESTORE | SW_SHOWNA;
}

placement.rcNormalPosition = newWindowRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;

::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365
::SetWindowPlacement(window, &placement);
return newWindowRect;
}

void Zone::StampZone(HWND window, bool stamp) noexcept
Expand Down
13 changes: 12 additions & 1 deletion src/modules/fancyzones/lib/Zone.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,20 @@ interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : pub
*/
IFACEMETHOD_(void, SetId)(size_t id) = 0;
/**
* @retirns Zone identifier.
* @returns Zone identifier.
*/
IFACEMETHOD_(size_t, Id)() = 0;

/**
* Compute the coordinates of the rectangle to which a window should be resized.
*
* @param window Handle of window which should be assigned to zone.
* @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @returns a RECT structure, describing global coordinates to which a window should be resized
*/
IFACEMETHOD_(RECT, ComputeActualZoneRect)(HWND window, HWND zoneWindow) = 0;

};

winrt::com_ptr<IZone> MakeZone(const RECT& zoneRect) noexcept;
154 changes: 120 additions & 34 deletions src/modules/fancyzones/lib/ZoneSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,16 @@ struct ZoneSet : winrt::implements<ZoneSet, IZoneSet>
IFACEMETHODIMP_(JSONHelpers::ZoneSetLayoutType)
LayoutType() noexcept { return m_config.LayoutType; }
IFACEMETHODIMP AddZone(winrt::com_ptr<IZone> zone) noexcept;
IFACEMETHODIMP_(winrt::com_ptr<IZone>)
ZoneFromPoint(POINT pt) noexcept;
IFACEMETHODIMP_(std::vector<int>)
ZonesFromPoint(POINT pt) noexcept;
IFACEMETHODIMP_(int)
GetZoneIndexFromWindow(HWND window) noexcept;
IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>)
GetZones() noexcept { return m_zones; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index, bool stampZone) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndexSet(HWND window, HWND windowZone, const std::vector<int>& indexSet, bool stampZone) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void)
Expand Down Expand Up @@ -162,41 +164,79 @@ IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr<IZone> zone) noexcept
return S_OK;
}

IFACEMETHODIMP_(winrt::com_ptr<IZone>)
ZoneSet::ZoneFromPoint(POINT pt) noexcept
IFACEMETHODIMP_(std::vector<int>)
ZoneSet::ZonesFromPoint(POINT pt) noexcept
{
winrt::com_ptr<IZone> smallestKnownZone = nullptr;
// To reduce redundant calculations, we will store the last known zones area.
int smallestKnownZoneArea = INT32_MAX;
for (auto iter = m_zones.rbegin(); iter != m_zones.rend(); iter++)
const int SENSITIVITY_RADIUS = 20;
std::vector<int> capturedZones;
std::vector<int> strictlyCapturedZones;
for (size_t i = 0; i < m_zones.size(); i++)
{
auto zone = m_zones[i];
RECT newZoneRect = zone->GetZoneRect();
if (newZoneRect.left < newZoneRect.right && newZoneRect.top < newZoneRect.bottom) // proper zone
{
if (newZoneRect.left - SENSITIVITY_RADIUS <= pt.x && pt.x <= newZoneRect.right + SENSITIVITY_RADIUS &&
newZoneRect.top - SENSITIVITY_RADIUS <= pt.y && pt.y <= newZoneRect.bottom + SENSITIVITY_RADIUS)
{
capturedZones.emplace_back(static_cast<int>(i));
}

if (newZoneRect.left <= pt.x && pt.x < newZoneRect.right &&
newZoneRect.top <= pt.y && pt.y < newZoneRect.bottom)
{
strictlyCapturedZones.emplace_back(static_cast<int>(i));
}
}
}

// 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)
{
if (winrt::com_ptr<IZone> zone = iter->try_as<IZone>())
return {};
}

// If captured zones do not overlap, return all of them
// Otherwise, return the smallest one

bool overlap = false;
for (size_t i = 0; i < capturedZones.size(); ++i)
{
for (size_t j = i + 1; j < capturedZones.size(); ++j)
{
RECT* newZoneRect = &zone->GetZoneRect();
if (PtInRect(newZoneRect, pt))
auto rectI = m_zones[capturedZones[i]]->GetZoneRect();
auto rectJ = m_zones[capturedZones[j]]->GetZoneRect();
if (max(rectI.top, rectJ.top) < min(rectI.bottom, rectJ.bottom) &&
max(rectI.left, rectJ.left) < min(rectI.right, rectJ.right))
{
if (smallestKnownZone == nullptr)
{
smallestKnownZone = zone;
overlap = true;
i = capturedZones.size() - 1;
break;
}
}
}

RECT* r = &smallestKnownZone->GetZoneRect();
smallestKnownZoneArea = (r->right - r->left) * (r->bottom - r->top);
}
else
{
int newZoneArea = (newZoneRect->right - newZoneRect->left) * (newZoneRect->bottom - newZoneRect->top);
if (overlap)
{
size_t smallestIdx = 0;
for (size_t i = 1; i < capturedZones.size(); ++i)
{
auto rectS = m_zones[capturedZones[smallestIdx]]->GetZoneRect();
auto rectI = m_zones[capturedZones[i]]->GetZoneRect();
int smallestSize = (rectS.bottom - rectS.top) * (rectS.right - rectS.left);
int iSize = (rectI.bottom - rectI.top) * (rectI.right - rectI.left);

if (newZoneArea < smallestKnownZoneArea)
{
smallestKnownZone = zone;
smallestKnownZoneArea = newZoneArea;
}
}
if (iSize <= smallestSize)
{
smallestIdx = i;
}
}

capturedZones = { capturedZones[smallestIdx] };
}

return smallestKnownZone;
return capturedZones;
}

IFACEMETHODIMP_(int)
Expand All @@ -217,7 +257,7 @@ ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept
}

IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noexcept
ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index, bool stampZone) noexcept
{
if (m_zones.empty())
{
Expand All @@ -236,7 +276,55 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex

if (auto zone = m_zones.at(index))
{
zone->AddWindowToZone(window, windowZone, false);
zone->AddWindowToZone(window, windowZone, stampZone);
}
}

IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND windowZone, const std::vector<int>& indexSet, bool stampZone) noexcept
{
if (m_zones.empty())
{
return;
}

while (auto zoneDrop = ZoneFromWindow(window))
{
zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window));
}

if (indexSet.size() == 1)
{
MoveWindowIntoZoneByIndex(window, windowZone, indexSet[0], stampZone);
return;
}

RECT size;
bool sizeEmpty = true;

for (int index : indexSet)
{
if (index < static_cast<int>(m_zones.size()))
{
RECT newSize = m_zones.at(index)->ComputeActualZoneRect(window, windowZone);
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;
}
}
}

if (!sizeEmpty)
{
SizeWindowToRect(window, size);
}
}

Expand Down Expand Up @@ -306,10 +394,8 @@ ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient)
zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window));
}

if (auto zone = ZoneFromPoint(ptClient))
{
zone->AddWindowToZone(window, zoneWindow, true);
}
auto zones = ZonesFromPoint(ptClient);
MoveWindowIntoZoneByIndexSet(window, zoneWindow, zones, true);
}

IFACEMETHODIMP_(bool)
Expand Down
20 changes: 16 additions & 4 deletions src/modules/fancyzones/lib/ZoneSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
*/
IFACEMETHOD(AddZone)(winrt::com_ptr<IZone> zone) = 0;
/**
* Get zone from cursor coordinates.
* Get zones from cursor coordinates.
*
* @param pt Cursor coordinates.
* @returns Zone object (defining coordinates of the zone).
* @returns Vector of indices, corresponding to the current set of zones - the zones considered active.
*/
IFACEMETHOD_(winrt::com_ptr<IZone>, ZoneFromPoint)(POINT pt) = 0;
IFACEMETHOD_(std::vector<int>, ZonesFromPoint)(POINT pt) = 0;
/**
* Get index of the zone inside zone layout by window assigned to it.
*
Expand All @@ -48,8 +48,20 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @param index Zone index within zone layout.
* @param stampZone Whether the window being added to the zone should be stamped.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND zoneWindow, int index) = 0;
IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND zoneWindow, int index, bool stampZone) = 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 zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @param indexSet The set of zone indices within zone layout.
* @param stampZone Whether the window being added to the zone should be stamped,
in case a single window is to be added.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND zoneWindow, const std::vector<int>& indexSet, bool stampZone) = 0;
/**
* Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow).
*
Expand Down
Loading

0 comments on commit 629ba76

Please sign in to comment.