Basic support for snapping to multiple zones (#1955)

* 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
This commit is contained in:
Ivan Stošić
2020-04-10 16:09:08 +02:00
committed by GitHub
parent e896e1b3dd
commit 629ba763d7
11 changed files with 328 additions and 128 deletions

View File

@@ -150,6 +150,7 @@ public:
void AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept; void AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept;
void MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int index) noexcept; void MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int index) noexcept;
void MoveWindowIntoZoneByIndexSet(HWND window, HMONITOR monitor, const std::vector<int>& indexSet) noexcept;
protected: protected:
static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
@@ -695,6 +696,12 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
} }
void FancyZones::MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int index) 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); std::shared_lock readLock(m_lock);
if (window != m_windowMoveSize) if (window != m_windowMoveSize)
@@ -706,7 +713,7 @@ void FancyZones::MoveWindowIntoZoneByIndex(HWND window, HMONITOR monitor, int in
if (zoneWindow != m_zoneWindowMap.end()) if (zoneWindow != m_zoneWindowMap.end())
{ {
const auto& zoneWindowPtr = zoneWindow->second; const auto& zoneWindowPtr = zoneWindow->second;
zoneWindowPtr->MoveWindowIntoZoneByIndex(window, index); zoneWindowPtr->MoveWindowIntoZoneByIndexSet(window, indexSet);
} }
} }
} }

View File

@@ -4,6 +4,7 @@
#include <common/monitors.h> #include <common/monitors.h>
#include "Zone.h" #include "Zone.h"
#include "Settings.h" #include "Settings.h"
#include "util.h"
struct Zone : winrt::implements<Zone, IZone> struct Zone : winrt::implements<Zone, IZone>
{ {
@@ -20,6 +21,7 @@ public:
IFACEMETHODIMP_(void) RemoveWindowFromZone(HWND window, bool restoreSize) noexcept; IFACEMETHODIMP_(void) RemoveWindowFromZone(HWND window, bool restoreSize) noexcept;
IFACEMETHODIMP_(void) SetId(size_t id) noexcept { m_id = id; } IFACEMETHODIMP_(void) SetId(size_t id) noexcept { m_id = id; }
IFACEMETHODIMP_(size_t) Id() noexcept { return m_id; } IFACEMETHODIMP_(size_t) Id() noexcept { return m_id; }
IFACEMETHODIMP_(RECT) ComputeActualZoneRect(HWND window, HWND zoneWindow) noexcept;
private: private:
void SizeWindowToZone(HWND window, HWND zoneWindow) noexcept; void SizeWindowToZone(HWND window, HWND zoneWindow) noexcept;
@@ -61,12 +63,11 @@ IFACEMETHODIMP_(void) Zone::RemoveWindowFromZone(HWND window, bool restoreSize)
void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
{ {
// Skip invisible windows SizeWindowToRect(window, ComputeActualZoneRect(window, zoneWindow));
if (!IsWindowVisible(window)) }
{
return;
}
RECT Zone::ComputeActualZoneRect(HWND window, HWND zoneWindow) noexcept
{
// Take care of 1px border // Take care of 1px border
RECT newWindowRect = m_zoneRect; RECT newWindowRect = m_zoneRect;
@@ -111,29 +112,7 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top); newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
} }
WINDOWPLACEMENT placement{}; return newWindowRect;
::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);
} }
void Zone::StampZone(HWND window, bool stamp) noexcept void Zone::StampZone(HWND window, bool stamp) noexcept

View File

@@ -42,9 +42,20 @@ interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : pub
*/ */
IFACEMETHOD_(void, SetId)(size_t id) = 0; IFACEMETHOD_(void, SetId)(size_t id) = 0;
/** /**
* @retirns Zone identifier. * @returns Zone identifier.
*/ */
IFACEMETHOD_(size_t, Id)() = 0; 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; winrt::com_ptr<IZone> MakeZone(const RECT& zoneRect) noexcept;

View File

@@ -122,14 +122,16 @@ public:
IFACEMETHODIMP_(JSONHelpers::ZoneSetLayoutType) IFACEMETHODIMP_(JSONHelpers::ZoneSetLayoutType)
LayoutType() noexcept { return m_config.LayoutType; } LayoutType() noexcept { return m_config.LayoutType; }
IFACEMETHODIMP AddZone(winrt::com_ptr<IZone> zone) noexcept; IFACEMETHODIMP AddZone(winrt::com_ptr<IZone> zone) noexcept;
IFACEMETHODIMP_(winrt::com_ptr<IZone>) IFACEMETHODIMP_(std::vector<int>)
ZoneFromPoint(POINT pt) noexcept; ZonesFromPoint(POINT pt) noexcept;
IFACEMETHODIMP_(int) IFACEMETHODIMP_(int)
GetZoneIndexFromWindow(HWND window) noexcept; GetZoneIndexFromWindow(HWND window) noexcept;
IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>) IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>)
GetZones() noexcept { return m_zones; } GetZones() noexcept { return m_zones; }
IFACEMETHODIMP_(void) 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) IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept; MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@@ -162,41 +164,79 @@ IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr<IZone> zone) noexcept
return S_OK; return S_OK;
} }
IFACEMETHODIMP_(winrt::com_ptr<IZone>) IFACEMETHODIMP_(std::vector<int>)
ZoneSet::ZoneFromPoint(POINT pt) noexcept ZoneSet::ZonesFromPoint(POINT pt) noexcept
{ {
winrt::com_ptr<IZone> smallestKnownZone = nullptr; const int SENSITIVITY_RADIUS = 20;
// To reduce redundant calculations, we will store the last known zones area. std::vector<int> capturedZones;
int smallestKnownZoneArea = INT32_MAX; std::vector<int> strictlyCapturedZones;
for (auto iter = m_zones.rbegin(); iter != m_zones.rend(); iter++) for (size_t i = 0; i < m_zones.size(); i++)
{ {
if (winrt::com_ptr<IZone> zone = iter->try_as<IZone>()) auto zone = m_zones[i];
RECT newZoneRect = zone->GetZoneRect();
if (newZoneRect.left < newZoneRect.right && newZoneRect.top < newZoneRect.bottom) // proper zone
{ {
RECT* newZoneRect = &zone->GetZoneRect(); if (newZoneRect.left - SENSITIVITY_RADIUS <= pt.x && pt.x <= newZoneRect.right + SENSITIVITY_RADIUS &&
if (PtInRect(newZoneRect, pt)) newZoneRect.top - SENSITIVITY_RADIUS <= pt.y && pt.y <= newZoneRect.bottom + SENSITIVITY_RADIUS)
{ {
if (smallestKnownZone == nullptr) capturedZones.emplace_back(static_cast<int>(i));
{ }
smallestKnownZone = zone;
if (newZoneRect.left <= pt.x && pt.x < newZoneRect.right &&
RECT* r = &smallestKnownZone->GetZoneRect(); newZoneRect.top <= pt.y && pt.y < newZoneRect.bottom)
smallestKnownZoneArea = (r->right - r->left) * (r->bottom - r->top); {
} strictlyCapturedZones.emplace_back(static_cast<int>(i));
else
{
int newZoneArea = (newZoneRect->right - newZoneRect->left) * (newZoneRect->bottom - newZoneRect->top);
if (newZoneArea < smallestKnownZoneArea)
{
smallestKnownZone = zone;
smallestKnownZoneArea = newZoneArea;
}
}
} }
} }
} }
return smallestKnownZone; // 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 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)
{
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))
{
overlap = true;
i = capturedZones.size() - 1;
break;
}
}
}
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 (iSize <= smallestSize)
{
smallestIdx = i;
}
}
capturedZones = { capturedZones[smallestIdx] };
}
return capturedZones;
} }
IFACEMETHODIMP_(int) IFACEMETHODIMP_(int)
@@ -217,7 +257,7 @@ ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept
} }
IFACEMETHODIMP_(void) 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()) if (m_zones.empty())
{ {
@@ -236,7 +276,55 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
if (auto zone = m_zones.at(index)) 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);
} }
} }
@@ -306,10 +394,8 @@ ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient)
zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window)); zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window));
} }
if (auto zone = ZoneFromPoint(ptClient)) auto zones = ZonesFromPoint(ptClient);
{ MoveWindowIntoZoneByIndexSet(window, zoneWindow, zones, true);
zone->AddWindowToZone(window, zoneWindow, true);
}
} }
IFACEMETHODIMP_(bool) IFACEMETHODIMP_(bool)

View File

@@ -24,12 +24,12 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
*/ */
IFACEMETHOD(AddZone)(winrt::com_ptr<IZone> zone) = 0; IFACEMETHOD(AddZone)(winrt::com_ptr<IZone> zone) = 0;
/** /**
* Get zone from cursor coordinates. * Get zones from cursor coordinates.
* *
* @param pt 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. * Get index of the zone inside zone layout by window assigned to it.
* *
@@ -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 * @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area. * current monitor desktop work area.
* @param index Zone index within zone layout. * @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). * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow).
* *

View File

@@ -148,7 +148,7 @@ namespace ZoneWindowDrawUtils
COLORREF highlightColor, COLORREF highlightColor,
int zoneOpacity, int zoneOpacity,
const std::vector<winrt::com_ptr<IZone>>& zones, const std::vector<winrt::com_ptr<IZone>>& zones,
const winrt::com_ptr<IZone>& highlightZone, const std::vector<int>& highlightZones,
bool flashMode, bool flashMode,
bool drawHints) noexcept bool drawHints) noexcept
{ {
@@ -158,15 +158,22 @@ namespace ZoneWindowDrawUtils
ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 }; ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 }; ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
std::vector<bool> isHighlighted(zones.size(), false);
for (int x : highlightZones)
{
isHighlighted[x] = true;
}
for (auto iter = zones.begin(); iter != zones.end(); iter++) for (auto iter = zones.begin(); iter != zones.end(); iter++)
{ {
int zoneId = static_cast<int>(iter - zones.begin());
winrt::com_ptr<IZone> zone = iter->try_as<IZone>(); winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
if (!zone) if (!zone)
{ {
continue; continue;
} }
if (zone != highlightZone) if (!isHighlighted[zoneId])
{ {
if (flashMode) if (flashMode)
{ {
@@ -182,13 +189,12 @@ namespace ZoneWindowDrawUtils
DrawZone(hdc, colorViewer, zone, zones, flashMode); DrawZone(hdc, colorViewer, zone, zones, flashMode);
} }
} }
} else
{
if (highlightZone) colorHighlight.fill = highlightColor;
{ colorHighlight.border = zoneBorderColor;
colorHighlight.fill = highlightColor; DrawZone(hdc, colorHighlight, zone, zones, flashMode);
colorHighlight.border = zoneBorderColor; }
DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode);
} }
} }
} }
@@ -210,6 +216,8 @@ public:
IsDragEnabled() noexcept { return m_dragEnabled; } IsDragEnabled() noexcept { return m_dragEnabled; }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, int index) noexcept; MoveWindowIntoZoneByIndex(HWND window, int index) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndexSet(HWND window, const std::vector<int>& indexSet) noexcept;
IFACEMETHODIMP_(bool) IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept; MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@@ -238,7 +246,7 @@ private:
LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept;
void OnPaint(wil::unique_hdc& hdc) noexcept; void OnPaint(wil::unique_hdc& hdc) noexcept;
void OnKeyUp(WPARAM wparam) noexcept; void OnKeyUp(WPARAM wparam) noexcept;
winrt::com_ptr<IZone> ZoneFromPoint(POINT pt) noexcept; std::vector<int> ZonesFromPoint(POINT pt) noexcept;
void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept; void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept;
void FlashZones() noexcept; void FlashZones() noexcept;
@@ -253,7 +261,7 @@ private:
bool m_dragEnabled{}; bool m_dragEnabled{};
winrt::com_ptr<IZoneSet> m_activeZoneSet; winrt::com_ptr<IZoneSet> m_activeZoneSet;
std::vector<winrt::com_ptr<IZoneSet>> m_zoneSets; std::vector<winrt::com_ptr<IZoneSet>> m_zoneSets;
winrt::com_ptr<IZone> m_highlightZone; std::vector<int> m_highlightZone;
WPARAM m_keyLast{}; WPARAM m_keyLast{};
size_t m_keyCycle{}; size_t m_keyCycle{};
static const UINT m_showAnimationDuration = 200; // ms static const UINT m_showAnimationDuration = 200; // ms
@@ -363,7 +371,7 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window, bool dragEnabled) noexcept
m_dragEnabled = dragEnabled; m_dragEnabled = dragEnabled;
m_windowMoveSize = window; m_windowMoveSize = window;
m_drawHints = true; m_drawHints = true;
m_highlightZone = nullptr; m_highlightZone = {};
ShowZoneWindow(); ShowZoneWindow();
return S_OK; return S_OK;
} }
@@ -378,13 +386,13 @@ IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnable
if (dragEnabled) if (dragEnabled)
{ {
auto highlightZone = ZoneFromPoint(ptClient); auto highlightZone = ZonesFromPoint(ptClient);
redraw = (highlightZone != m_highlightZone); redraw = (highlightZone != m_highlightZone);
m_highlightZone = std::move(highlightZone); m_highlightZone = std::move(highlightZone);
} }
else if (m_highlightZone) else if (m_highlightZone.size())
{ {
m_highlightZone = nullptr; m_highlightZone = {};
redraw = true; redraw = true;
} }
@@ -432,10 +440,16 @@ ZoneWindow::RestoreOrginalTransparency() noexcept
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept
{
MoveWindowIntoZoneByIndexSet(window, { index });
}
IFACEMETHODIMP_(void)
ZoneWindow::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector<int>& indexSet) noexcept
{ {
if (m_activeZoneSet) if (m_activeZoneSet)
{ {
m_activeZoneSet->MoveWindowIntoZoneByIndex(window, m_window.get(), index); m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window.get(), indexSet, false);
} }
} }
@@ -518,7 +532,7 @@ ZoneWindow::HideZoneWindow() noexcept
m_keyLast = 0; m_keyLast = 0;
m_windowMoveSize = nullptr; m_windowMoveSize = nullptr;
m_drawHints = false; m_drawHints = false;
m_highlightZone = nullptr; m_highlightZone = {};
} }
} }
@@ -685,13 +699,13 @@ void ZoneWindow::OnKeyUp(WPARAM wparam) noexcept
} }
} }
winrt::com_ptr<IZone> ZoneWindow::ZoneFromPoint(POINT pt) noexcept std::vector<int> ZoneWindow::ZonesFromPoint(POINT pt) noexcept
{ {
if (m_activeZoneSet) if (m_activeZoneSet)
{ {
return m_activeZoneSet->ZoneFromPoint(pt); return m_activeZoneSet->ZonesFromPoint(pt);
} }
return nullptr; return {};
} }
void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept
@@ -739,7 +753,7 @@ void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::Inp
{ {
m_host->MoveWindowsOnActiveZoneSetChange(); m_host->MoveWindowsOnActiveZoneSetChange();
} }
m_highlightZone = nullptr; m_highlightZone = {};
} }
void ZoneWindow::FlashZones() noexcept void ZoneWindow::FlashZones() noexcept

View File

@@ -53,6 +53,13 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* @param index Zone index within zone layout. * @param index Zone index within zone layout.
*/ */
IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, int index) = 0; IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, int 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 indexSet The set of zone indices within zone layout.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, const std::vector<int>& indexSet) = 0;
/** /**
* Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow). * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow).
* *

View File

@@ -108,3 +108,30 @@ void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
monitorInfo = std::move(sortedMonitorInfo); monitorInfo = std::move(sortedMonitorInfo);
} }
void SizeWindowToRect(HWND window, RECT rect) noexcept
{
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 = rect;
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);
}

View File

@@ -118,3 +118,4 @@ inline BYTE OpacitySettingToAlpha(int opacity)
UINT GetDpiForMonitor(HMONITOR monitor) noexcept; UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo); void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
void SizeWindowToRect(HWND window, RECT rect) noexcept;

View File

@@ -132,8 +132,8 @@ namespace FancyZonesUnitTests
TEST_METHOD (ZoneFromPointEmpty) TEST_METHOD (ZoneFromPointEmpty)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ 0, 0 }); auto actual = m_set->ZonesFromPoint(POINT{ 0, 0 });
Assert::IsTrue(nullptr == actual); Assert::IsTrue(actual.size() == 0);
} }
TEST_METHOD (ZoneFromPointInner) TEST_METHOD (ZoneFromPointInner)
@@ -146,9 +146,9 @@ namespace FancyZonesUnitTests
{ {
for (int j = top + 1; j < bottom; j++) for (int j = top + 1; j < bottom; j++)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ i, j }); auto actual = m_set->ZonesFromPoint(POINT{ i, j });
Assert::IsTrue(actual != nullptr); Assert::IsTrue(actual.size() == 1);
compareZones(expected, actual); compareZones(expected, m_set->GetZones()[actual[0]]);
} }
} }
} }
@@ -161,29 +161,29 @@ namespace FancyZonesUnitTests
for (int i = left; i < right; i++) for (int i = left; i < right; i++)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ i, top }); auto actual = m_set->ZonesFromPoint(POINT{ i, top });
Assert::IsTrue(actual != nullptr); Assert::IsTrue(actual.size() == 1);
compareZones(expected, actual); compareZones(expected, m_set->GetZones()[actual[0]]);
} }
for (int i = top; i < bottom; i++) for (int i = top; i < bottom; i++)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ left, i }); auto actual = m_set->ZonesFromPoint(POINT{ left, i });
Assert::IsTrue(actual != nullptr); Assert::IsTrue(actual.size() == 1);
compareZones(expected, actual); compareZones(expected, m_set->GetZones()[actual[0]]);
} }
//bottom and right borders considered to be outside //bottom and right borders considered to be outside
for (int i = left; i < right; i++) for (int i = left; i < right; i++)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ i, bottom }); auto actual = m_set->ZonesFromPoint(POINT{ i, bottom });
Assert::IsTrue(nullptr == actual); Assert::IsTrue(actual.size() == 0);
} }
for (int i = top; i < bottom; i++) for (int i = top; i < bottom; i++)
{ {
auto actual = m_set->ZoneFromPoint(POINT{ right, i }); auto actual = m_set->ZonesFromPoint(POINT{ right, i });
Assert::IsTrue(nullptr == actual); Assert::IsTrue(actual.size() == 0);
} }
} }
@@ -193,8 +193,8 @@ namespace FancyZonesUnitTests
winrt::com_ptr<IZone> zone = MakeZone({ left, top, right, bottom }); winrt::com_ptr<IZone> zone = MakeZone({ left, top, right, bottom });
m_set->AddZone(zone); m_set->AddZone(zone);
auto actual = m_set->ZoneFromPoint(POINT{ 101, 101 }); auto actual = m_set->ZonesFromPoint(POINT{ 200, 200 });
Assert::IsTrue(actual == nullptr); Assert::IsTrue(actual.size() == 0);
} }
TEST_METHOD (ZoneFromPointOverlapping) TEST_METHOD (ZoneFromPointOverlapping)
@@ -208,9 +208,65 @@ namespace FancyZonesUnitTests
winrt::com_ptr<IZone> zone4 = MakeZone({ 10, 10, 50, 50 }); winrt::com_ptr<IZone> zone4 = MakeZone({ 10, 10, 50, 50 });
m_set->AddZone(zone4); m_set->AddZone(zone4);
auto actual = m_set->ZoneFromPoint(POINT{ 50, 50 }); // zone4 is expected because it's the smallest one, and it's considered to be inside
Assert::IsTrue(actual != nullptr); // since Multizones support
compareZones(zone2, actual);
auto actual = m_set->ZonesFromPoint(POINT{ 50, 50 });
Assert::IsTrue(actual.size() == 1);
compareZones(zone4, m_set->GetZones()[actual[0]]);
}
TEST_METHOD (ZoneFromPointMultizoneHorizontal)
{
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 });
m_set->AddZone(zone1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 100, 0, 200, 100 });
m_set->AddZone(zone2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 0, 100, 100, 200 });
m_set->AddZone(zone3);
winrt::com_ptr<IZone> zone4 = MakeZone({ 100, 100, 200, 200 });
m_set->AddZone(zone4);
auto actual = m_set->ZonesFromPoint(POINT{ 50, 100 });
Assert::IsTrue(actual.size() == 2);
compareZones(zone1, m_set->GetZones()[actual[0]]);
compareZones(zone3, m_set->GetZones()[actual[1]]);
}
TEST_METHOD (ZoneFromPointMultizoneVertical)
{
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 });
m_set->AddZone(zone1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 100, 0, 200, 100 });
m_set->AddZone(zone2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 0, 100, 100, 200 });
m_set->AddZone(zone3);
winrt::com_ptr<IZone> zone4 = MakeZone({ 100, 100, 200, 200 });
m_set->AddZone(zone4);
auto actual = m_set->ZonesFromPoint(POINT{ 100, 50 });
Assert::IsTrue(actual.size() == 2);
compareZones(zone1, m_set->GetZones()[actual[0]]);
compareZones(zone2, m_set->GetZones()[actual[1]]);
}
TEST_METHOD(ZoneFromPointMultizoneQuad)
{
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 });
m_set->AddZone(zone1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 100, 0, 200, 100 });
m_set->AddZone(zone2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 0, 100, 100, 200 });
m_set->AddZone(zone3);
winrt::com_ptr<IZone> zone4 = MakeZone({ 100, 100, 200, 200 });
m_set->AddZone(zone4);
auto actual = m_set->ZonesFromPoint(POINT{ 100, 100 });
Assert::IsTrue(actual.size() == 4);
compareZones(zone1, m_set->GetZones()[actual[0]]);
compareZones(zone2, m_set->GetZones()[actual[1]]);
compareZones(zone3, m_set->GetZones()[actual[2]]);
compareZones(zone4, m_set->GetZones()[actual[3]]);
} }
TEST_METHOD (ZoneFromPointWithNotNormalizedRect) TEST_METHOD (ZoneFromPointWithNotNormalizedRect)
@@ -218,8 +274,8 @@ namespace FancyZonesUnitTests
winrt::com_ptr<IZone> zone = MakeZone({ 100, 100, 0, 0 }); winrt::com_ptr<IZone> zone = MakeZone({ 100, 100, 0, 0 });
m_set->AddZone(zone); m_set->AddZone(zone);
auto actual = m_set->ZoneFromPoint(POINT{ 50, 50 }); auto actual = m_set->ZonesFromPoint(POINT{ 50, 50 });
Assert::IsTrue(actual == nullptr); Assert::IsTrue(actual.size() == 0);
} }
TEST_METHOD (ZoneFromPointWithZeroRect) TEST_METHOD (ZoneFromPointWithZeroRect)
@@ -227,8 +283,8 @@ namespace FancyZonesUnitTests
winrt::com_ptr<IZone> zone = MakeZone({ 0, 0, 0, 0 }); winrt::com_ptr<IZone> zone = MakeZone({ 0, 0, 0, 0 });
m_set->AddZone(zone); m_set->AddZone(zone);
auto actual = m_set->ZoneFromPoint(POINT{ 0, 0 }); auto actual = m_set->ZonesFromPoint(POINT{ 0, 0 });
Assert::IsTrue(actual == nullptr); Assert::IsTrue(actual.size() == 0);
} }
TEST_METHOD (ZoneIndexFromWindow) TEST_METHOD (ZoneIndexFromWindow)
@@ -316,7 +372,7 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone3); m_set->AddZone(zone3);
HWND window = Mocks::Window(); HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1, false);
Assert::IsFalse(zone1->ContainsWindow(window)); Assert::IsFalse(zone1->ContainsWindow(window));
Assert::IsTrue(zone2->ContainsWindow(window)); Assert::IsTrue(zone2->ContainsWindow(window));
Assert::IsFalse(zone3->ContainsWindow(window)); Assert::IsFalse(zone3->ContainsWindow(window));
@@ -325,7 +381,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByIndexWithNoZones) TEST_METHOD (MoveWindowIntoZoneByIndexWithNoZones)
{ {
HWND window = Mocks::Window(); HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
} }
TEST_METHOD (MoveWindowIntoZoneByIndexWithInvalidIndex) TEST_METHOD (MoveWindowIntoZoneByIndexWithInvalidIndex)
@@ -338,7 +394,7 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone3); m_set->AddZone(zone3);
HWND window = Mocks::Window(); HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100, false);
Assert::IsFalse(zone1->ContainsWindow(window)); Assert::IsFalse(zone1->ContainsWindow(window));
Assert::IsFalse(zone2->ContainsWindow(window)); Assert::IsFalse(zone2->ContainsWindow(window));
Assert::IsFalse(zone3->ContainsWindow(window)); Assert::IsFalse(zone3->ContainsWindow(window));
@@ -355,17 +411,17 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone3); m_set->AddZone(zone3);
HWND window = Mocks::Window(); HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
Assert::IsTrue(zone1->ContainsWindow(window)); Assert::IsTrue(zone1->ContainsWindow(window));
Assert::IsFalse(zone2->ContainsWindow(window)); Assert::IsFalse(zone2->ContainsWindow(window));
Assert::IsFalse(zone3->ContainsWindow(window)); Assert::IsFalse(zone3->ContainsWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1, false);
Assert::IsFalse(zone1->ContainsWindow(window)); Assert::IsFalse(zone1->ContainsWindow(window));
Assert::IsTrue(zone2->ContainsWindow(window)); Assert::IsTrue(zone2->ContainsWindow(window));
Assert::IsFalse(zone3->ContainsWindow(window)); Assert::IsFalse(zone3->ContainsWindow(window));
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2, false);
Assert::IsFalse(zone1->ContainsWindow(window)); Assert::IsFalse(zone1->ContainsWindow(window));
Assert::IsFalse(zone2->ContainsWindow(window)); Assert::IsFalse(zone2->ContainsWindow(window));
Assert::IsTrue(zone3->ContainsWindow(window)); Assert::IsTrue(zone3->ContainsWindow(window));
@@ -382,9 +438,9 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone3); m_set->AddZone(zone3);
HWND window = Mocks::Window(); HWND window = Mocks::Window();
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
Assert::IsTrue(zone1->ContainsWindow(window)); Assert::IsTrue(zone1->ContainsWindow(window));
Assert::IsFalse(zone2->ContainsWindow(window)); Assert::IsFalse(zone2->ContainsWindow(window));
Assert::IsFalse(zone3->ContainsWindow(window)); Assert::IsFalse(zone3->ContainsWindow(window));
@@ -401,7 +457,7 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone1); m_set->AddZone(zone1);
auto window = Mocks::Window(); auto window = Mocks::Window();
m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 101, 101 }); m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 200, 200 });
Assert::IsFalse(zone1->ContainsWindow(window)); Assert::IsFalse(zone1->ContainsWindow(window));
} }

View File

@@ -445,7 +445,7 @@ namespace FancyZonesUnitTests
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
const auto zoneSet = zoneWindow->ActiveZoneSet(); const auto zoneSet = zoneWindow->ActiveZoneSet();
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false); zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window); const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window);
Assert::AreNotEqual(-1, actualZoneIndex); Assert::AreNotEqual(-1, actualZoneIndex);
} }
@@ -458,7 +458,7 @@ namespace FancyZonesUnitTests
zoneWindow->MoveSizeEnter(window, true); zoneWindow->MoveSizeEnter(window, true);
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ 0, 0 }); const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ -100, -100 });
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
const auto zoneSet = zoneWindow->ActiveZoneSet(); const auto zoneSet = zoneWindow->ActiveZoneSet();
@@ -501,7 +501,7 @@ namespace FancyZonesUnitTests
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
const auto zoneSet = zoneWindow->ActiveZoneSet(); const auto zoneSet = zoneWindow->ActiveZoneSet();
zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false); zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0, false);
const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window); const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window);
Assert::AreNotEqual(-1, actualZoneIndex); //with invalid point zone remains the same Assert::AreNotEqual(-1, actualZoneIndex); //with invalid point zone remains the same
} }