From 9d9df949efebae1f8131482fc53cbb17e8b87725 Mon Sep 17 00:00:00 2001 From: FLOAT4 <17330977+FLOAT4@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:11:42 +0200 Subject: [PATCH] Add keyboard shortcuts (without GUI) for switching windows in the same zone (tabs) (#13973) Authored-by: float4 --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 81 ++++++++--- .../FancyZonesWindowProperties.h | 27 ++++ .../fancyzones/FancyZonesLib/Resources.resx | 9 ++ .../fancyzones/FancyZonesLib/Settings.cpp | 20 ++- .../fancyzones/FancyZonesLib/Settings.h | 3 + .../FancyZonesLib/WindowMoveHandler.cpp | 15 +- .../FancyZonesLib/WindowMoveHandler.h | 2 +- .../fancyzones/FancyZonesLib/WorkArea.cpp | 17 ++- .../fancyzones/FancyZonesLib/WorkArea.h | 10 +- .../fancyzones/FancyZonesLib/ZoneSet.cpp | 132 +++++++++++++++++- .../fancyzones/FancyZonesLib/ZoneSet.h | 18 ++- .../fancyzones/FancyZonesLib/trace.cpp | 30 ++-- .../UnitTests/FancyZones.Spec.cpp | 3 + .../UnitTests/FancyZonesSettings.Spec.cpp | 29 +++- .../FZConfigProperties.cs | 22 ++- .../ViewModels/FancyZonesViewModel.cs | 89 +++++++++++- .../ViewModelTests/FancyZones.cs | 3 + .../Strings/en-us/Resources.resw | 18 +++ .../Views/FancyZonesPage.xaml | 25 ++++ 19 files changed, 507 insertions(+), 46 deletions(-) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index cf4df48a50..38645b6562 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -147,7 +147,8 @@ protected: private: void UpdateZoneWindows() noexcept; - void UpdateWindowsPositions() noexcept; + void UpdateWindowsPositions(bool suppressMove = false) noexcept; + void CycleTabs(bool reverse) noexcept; bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkey(DWORD vkCode) noexcept; @@ -155,6 +156,7 @@ private: void RegisterVirtualDesktopUpdates() noexcept; + void UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept; void OnSettingsChanged() noexcept; std::pair, ZoneIndexSet> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, const std::unordered_map>& workAreaMap) noexcept; @@ -201,6 +203,14 @@ private: Exit, Terminate }; + + // IDs used to register hot keys (keyboard shortcuts). + enum class HotkeyId : int + { + Editor = 1, + NextTab = 2, + PrevTab = 3, + }; }; std::function FancyZones::disableModuleCallback = {}; @@ -224,7 +234,12 @@ FancyZones::Run() noexcept return; } - RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); + RegisterHotKey(m_window, static_cast(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); + if (m_settings->GetSettings()->windowSwitching) + { + RegisterHotKey(m_window, static_cast(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey.get_modifiers(), m_settings->GetSettings()->nextTabHotkey.get_code()); + RegisterHotKey(m_window, static_cast(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey.get_modifiers(), m_settings->GetSettings()->prevTabHotkey.get_code()); + } m_virtualDesktop.Init(); @@ -639,10 +654,15 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa { case WM_HOTKEY: { - if (wparam == 1) + if (wparam == static_cast(HotkeyId::Editor)) { ToggleEditor(); } + else if (wparam == static_cast(HotkeyId::NextTab) || wparam == static_cast(HotkeyId::PrevTab)) + { + bool reverse = wparam == static_cast(HotkeyId::PrevTab); + CycleTabs(reverse); + } } break; @@ -787,6 +807,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept UpdateZoneWindows(); + if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) { if (m_settings->GetSettings()->displayChange_moveWindows) @@ -897,7 +918,7 @@ void FancyZones::UpdateZoneWindows() noexcept } } -void FancyZones::UpdateWindowsPositions() noexcept +void FancyZones::UpdateWindowsPositions(bool suppressMove) noexcept { for (const auto [window, desktopId] : m_virtualDesktop.GetWindowsRelatedToDesktops()) { @@ -905,11 +926,23 @@ void FancyZones::UpdateWindowsPositions() noexcept auto zoneWindow = m_workAreaHandler.GetWorkArea(window, desktopId); if (zoneWindow) { - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow, suppressMove); } } } +void FancyZones::CycleTabs(bool reverse) noexcept +{ + auto window = GetForegroundWindow(); + HMONITOR current = WorkAreaKeyFromWindow(window); + + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); + if (workArea) + { + workArea->CycleTabs(window, reverse); + } +} + bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept { _TRACER_; @@ -1135,21 +1168,36 @@ void FancyZones::RegisterVirtualDesktopUpdates() noexcept FancyZonesDataInstance().SyncVirtualDesktops(m_currentDesktopId); } -void FancyZones::OnSettingsChanged() noexcept +void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept { - _TRACER_; - m_settings->ReloadSettings(); + UnregisterHotKey(m_window, hotkeyId); - // Update the hotkey - UnregisterHotKey(m_window, 1); - auto modifiers = m_settings->GetSettings()->editorHotkey.get_modifiers(); - auto code = m_settings->GetSettings()->editorHotkey.get_code(); - auto result = RegisterHotKey(m_window, 1, modifiers, code); + if (!enable) + { + return; + } + + auto modifiers = hotkeyObject.get_modifiers(); + auto code = hotkeyObject.get_code(); + auto result = RegisterHotKey(m_window, hotkeyId, modifiers, code); if (!result) { Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError())); } +} + +void FancyZones::OnSettingsChanged() noexcept +{ + _TRACER_; + m_settings->ReloadSettings(); + + // Update the hotkeys + UpdateHotkey(static_cast(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey, true); + + auto windowSwitching = m_settings->GetSettings()->windowSwitching; + UpdateHotkey(static_cast(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey, windowSwitching); + UpdateHotkey(static_cast(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey, windowSwitching); // Needed if we toggled spanZonesAcrossMonitors m_workAreaHandler.Clear(); @@ -1177,10 +1225,9 @@ void FancyZones::UpdateZoneSets() noexcept { workArea->UpdateActiveZoneSet(); } - if (m_settings->GetSettings()->zoneSetChange_moveWindows) - { - UpdateWindowsPositions(); - } + + auto moveWindows = m_settings->GetSettings()->zoneSetChange_moveWindows; + UpdateWindowsPositions(!moveWindows); } bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h index 0a395f9ead..39942359db 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -8,6 +9,7 @@ namespace ZonedWindowProperties { const wchar_t PropertyMultipleZoneID[] = L"FancyZones_zones"; + const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone"; const wchar_t PropertyRestoreSizeID[] = L"FancyZones_RestoreSize"; const wchar_t PropertyRestoreOriginID[] = L"FancyZones_RestoreOrigin"; @@ -44,3 +46,28 @@ inline void StampWindow(HWND window, Bitmask bitmask) noexcept memcpy(&rawData, data.data(), sizeof data); SetProp(window, ZonedWindowProperties::PropertyMultipleZoneID, rawData); } + +inline std::optional GetTabSortKeyWithinZone(HWND window) +{ + auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); + if (rawTabSortKeyWithinZone == NULL) + { + return std::nullopt; + } + + auto tabSortKeyWithinZone = reinterpret_cast(rawTabSortKeyWithinZone) - 1; + return tabSortKeyWithinZone; +} + +inline void SetTabSortKeyWithinZone(HWND window, std::optional tabSortKeyWithinZone) +{ + if (!tabSortKeyWithinZone.has_value()) + { + ::RemovePropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); + } + else + { + auto rawTabSortKeyWithinZone = reinterpret_cast(tabSortKeyWithinZone.value() + 1); + ::SetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone, rawTabSortKeyWithinZone); + } +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/Resources.resx b/src/modules/fancyzones/FancyZonesLib/Resources.resx index 44f77e9a4e..1575de1130 100644 --- a/src/modules/fancyzones/FancyZonesLib/Resources.resx +++ b/src/modules/fancyzones/FancyZonesLib/Resources.resx @@ -196,6 +196,15 @@ Configure the zone editor hotkey + + Toggle shortcuts for switching between windows in the current zone + + + Shortcut for switching to the next window in the current zone + + + Shortcut for switching to the previous window in the current zone + To exclude an application from snapping to zones add its name here (one per line). Excluded apps will react to the Windows Snap regardless of all other settings. Windows refers to the Operating system diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.cpp b/src/modules/fancyzones/FancyZonesLib/Settings.cpp index e9f95ea584..5b4b932fb1 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -32,6 +32,9 @@ namespace NonLocalizable const wchar_t ZoneBorderColorID[] = L"fancyzones_zoneBorderColor"; const wchar_t ZoneHighlightColorID[] = L"fancyzones_zoneHighlightColor"; const wchar_t EditorHotkeyID[] = L"fancyzones_editor_hotkey"; + const wchar_t WindowSwitchingToggleID[] = L"fancyzones_windowSwitching"; + const wchar_t NextTabHotkeyID[] = L"fancyzones_nextTab_hotkey"; + const wchar_t PrevTabHotkeyID[] = L"fancyzones_prevTab_hotkey"; const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps"; const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity"; @@ -77,7 +80,7 @@ private: PCWSTR name; bool* value; int resourceId; - } m_configBools[16] = { + } m_configBools[17] = { { NonLocalizable::ShiftDragID, &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG }, { NonLocalizable::MouseSwitchID, &m_settings.mouseSwitch, IDS_SETTING_DESCRIPTION_MOUSESWITCH }, { NonLocalizable::OverrideSnapHotKeysID, &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS }, @@ -94,6 +97,7 @@ private: { NonLocalizable::ShowOnAllMonitorsID, &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS }, { NonLocalizable::SpanZonesAcrossMonitorsID, &m_settings.spanZonesAcrossMonitors, IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS }, { NonLocalizable::MakeDraggedWindowTransparentID, &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT }, + { NonLocalizable::WindowSwitchingToggleID, &m_settings.windowSwitching, IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL }, }; }; @@ -116,6 +120,8 @@ FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_size) noexce IDS_SETTING_LAUNCH_EDITOR_BUTTON, IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey); + settings.add_hotkey(NonLocalizable::NextTabHotkeyID, IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, m_settings.nextTabHotkey); + settings.add_hotkey(NonLocalizable::PrevTabHotkeyID, IDS_SETTING_PREV_TAB_HOTKEY_LABEL, m_settings.prevTabHotkey); for (auto const& setting : m_configBools) { @@ -182,6 +188,16 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(*val); } + if (const auto val = values.get_json(NonLocalizable::NextTabHotkeyID)) + { + m_settings.nextTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + + if (const auto val = values.get_json(NonLocalizable::PrevTabHotkeyID)) + { + m_settings.prevTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID)) { m_settings.excludedApps = std::move(*val); @@ -246,6 +262,8 @@ void FancyZonesSettings::SaveSettings() noexcept values.add_property(NonLocalizable::ZoneHighlightOpacityID, m_settings.zoneHighlightOpacity); values.add_property(NonLocalizable::OverlappingZonesAlgorithmID, (int)m_settings.overlappingZonesAlgorithm); values.add_property(NonLocalizable::EditorHotkeyID, m_settings.editorHotkey.get_json()); + values.add_property(NonLocalizable::NextTabHotkeyID, m_settings.nextTabHotkey.get_json()); + values.add_property(NonLocalizable::PrevTabHotkeyID, m_settings.prevTabHotkey.get_json()); values.add_property(NonLocalizable::ExcludedAppsID, m_settings.excludedApps); values.save_to_settings_file(); diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index dac2c992e9..1aa5f4b381 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -38,6 +38,9 @@ struct Settings int zoneHighlightOpacity = 50; OverlappingZonesAlgorithm overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, VK_OEM_3); + bool windowSwitching = true; + PowerToysSettings::HotkeyObject nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_NEXT); + PowerToysSettings::HotkeyObject prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_PRIOR); std::wstring excludedApps = L""; std::vector excludedAppsArray; }; diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp index 77048d4e36..87e12404c5 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp @@ -123,6 +123,17 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const } } } + + auto zoneWindow = zoneWindowMap.find(monitor); + if (zoneWindow != zoneWindowMap.end()) + { + const auto zoneWindowPtr = zoneWindow->second; + const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); + if (activeZoneSet) + { + activeZoneSet->DismissWindow(window); + } + } } void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept @@ -273,11 +284,11 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st } } -void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept +void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow, bool suppressMove) noexcept { if (window != m_windowMoveSize) { - zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet); + zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet, suppressMove); } } diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h index 48eeaae2f9..2677e9a243 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h @@ -18,7 +18,7 @@ public: void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept; + void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow, bool suppressMove = false) noexcept; bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 4098192933..160d380b6c 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -117,7 +117,7 @@ public: IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept; IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept; + MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool suppressMove = false) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) @@ -139,6 +139,8 @@ public: IFACEMETHODIMP_(void) UpdateActiveZoneSet() noexcept; IFACEMETHODIMP_(void) + CycleTabs(HWND window, bool reverse) noexcept; + IFACEMETHODIMP_(void) ClearSelectedZones() noexcept; IFACEMETHODIMP_(void) FlashZones() noexcept; @@ -311,11 +313,11 @@ WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept } IFACEMETHODIMP_(void) -WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept +WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool suppressMove) noexcept { if (m_activeZoneSet) { - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); + m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet, suppressMove); } } @@ -431,6 +433,15 @@ WorkArea::UpdateActiveZoneSet() noexcept } } +IFACEMETHODIMP_(void) +WorkArea::CycleTabs(HWND window, bool reverse) noexcept +{ + if (m_activeZoneSet) + { + m_activeZoneSet->CycleTabs(window, reverse); + } +} + IFACEMETHODIMP_(void) WorkArea::ClearSelectedZones() noexcept { diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 85b42744b2..b0dbc47ea2 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -51,8 +51,9 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea : * * @param window Handle of window which should be assigned to zone. * @param indexSet The set of zone indices within zone layout. + * @param suppressMove Whether we should just update the records or move window to the zone. */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, const ZoneIndexSet& indexSet) = 0; + IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, const ZoneIndexSet& indexSet, bool suppressMove = false) = 0; /** * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow), based on zone index numbers, * not their on-screen position. @@ -113,6 +114,13 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea : * Update currently active zone layout for this work area. */ IFACEMETHOD_(void, UpdateActiveZoneSet)() = 0; + /** + * Cycle through tabs in the zone that the window is in. + * + * @param window Handle of window which is cycled from (the current tab). + * @param reverse Whether to cycle in reverse order (to the previous tab) or to move to the next tab. + */ + IFACEMETHOD_(void, CycleTabs)(HWND window, bool reverse) = 0; /** * Clear the selected zones when this WorkArea loses focus. */ diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp index c89ba458db..c0995a44aa 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp @@ -130,7 +130,7 @@ public: IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex index) noexcept; IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) noexcept; + MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet, bool suppressMove = false) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) @@ -139,6 +139,10 @@ public: ExtendWindowByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode) noexcept; IFACEMETHODIMP_(void) MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptClient) noexcept; + IFACEMETHODIMP_(void) + DismissWindow(HWND window) noexcept; + IFACEMETHODIMP_(void) + CycleTabs(HWND window, bool reverse) noexcept; IFACEMETHODIMP_(bool) CalculateZones(RECT workArea, int zoneCount, int spacing) noexcept; IFACEMETHODIMP_(bool) IsZoneEmpty(ZoneIndex zoneIndex) const noexcept; @@ -151,6 +155,8 @@ private: 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); + HWND GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept; + void InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet); ZoneIndexSet ZoneSelectSubregion(const ZoneIndexSet& capturedZones, POINT pt) const; ZoneIndexSet ZoneSelectClosestCenter(const ZoneIndexSet& capturedZones, POINT pt) const; @@ -160,6 +166,7 @@ private: ZonesMap m_zones; std::map m_windowIndexSet; + std::map> m_windowsByIndexSets; // Needed for ExtendWindowByDirectionAndPosition std::map m_windowInitialIndexSet; @@ -289,7 +296,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex i } IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& zoneIds) noexcept +ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& zoneIds, bool suppressMove) noexcept { if (m_zones.empty()) { @@ -303,11 +310,13 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo m_windowInitialIndexSet.erase(window); } + auto tabSortKeyWithinZone = GetTabSortKeyWithinZone(window); + DismissWindow(window); + RECT size; bool sizeEmpty = true; Bitmask bitmask = 0; - - m_windowIndexSet[window] = {}; + auto& indexSet = m_windowIndexSet[window]; for (ZoneIndex id : zoneIds) { @@ -328,7 +337,7 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo sizeEmpty = false; } - m_windowIndexSet[window].push_back(id); + indexSet.push_back(id); } if (id < std::numeric_limits::digits) @@ -339,9 +348,14 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo if (!sizeEmpty) { - SaveWindowSizeAndOrigin(window); - SizeWindowToRect(window, size); + if (!suppressMove) + { + SaveWindowSizeAndOrigin(window); + SizeWindowToRect(window, size); + } + StampWindow(window, bitmask); + InsertTabIntoZone(window, tabSortKeyWithinZone, indexSet); } } @@ -546,6 +560,110 @@ ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptCli MoveWindowIntoZoneByIndexSet(window, workAreaWindow, zones); } +void ZoneSet::DismissWindow(HWND window) noexcept +{ + auto& indexSet = m_windowIndexSet[window]; + if (!indexSet.empty()) + { + auto& windows = m_windowsByIndexSets[indexSet]; + windows.erase(find(begin(windows), end(windows), window)); + if (windows.empty()) + { + m_windowsByIndexSets.erase(indexSet); + } + + indexSet.clear(); + } + + SetTabSortKeyWithinZone(window, std::nullopt); +} + +IFACEMETHODIMP_(void) +ZoneSet::CycleTabs(HWND window, bool reverse) noexcept +{ + auto indexSet = GetZoneIndexSetFromWindow(window); + + // Do nothing in case the window is not recognized + if (indexSet.empty()) + { + return; + } + + for (;;) + { + auto next = GetNextTab(indexSet, window, reverse); + if (next == NULL) + { + break; + } + + auto success = SetForegroundWindow(next); + if (!success && GetLastError() == ERROR_INVALID_WINDOW_HANDLE) + { + // Dismiss the encountered window since it was probably closed + DismissWindow(next); + continue; + } + + break; + } +} + +HWND ZoneSet::GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept +{ + const auto& tabs = m_windowsByIndexSets[indexSet]; + auto tabIt = std::find(tabs.begin(), tabs.end(), current); + if (!reverse) + { + ++tabIt; + return tabIt == tabs.end() ? tabs.front() : *tabIt; + } + else + { + return tabIt == tabs.begin() ? tabs.back() : *(--tabIt); + } +} + +void ZoneSet::InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) +{ + if (tabSortKeyWithinZone.has_value()) + { + // Insert the tab using the provided sort key + auto predicate = [tabSortKeyWithinZone](HWND tab) { + auto currentTabSortKeyWithinZone = GetTabSortKeyWithinZone(tab); + if (currentTabSortKeyWithinZone.has_value()) + { + return currentTabSortKeyWithinZone.value() > tabSortKeyWithinZone; + } + else + { + return false; + } + }; + + auto position = std::find_if(m_windowsByIndexSets[indexSet].begin(), m_windowsByIndexSets[indexSet].end(), predicate); + m_windowsByIndexSets[indexSet].insert(position, window); + } + else + { + // Insert the tab at the end + tabSortKeyWithinZone = 0; + if (!m_windowsByIndexSets[indexSet].empty()) + { + auto prevTab = m_windowsByIndexSets[indexSet].back(); + auto prevTabSortKeyWithinZone = GetTabSortKeyWithinZone(prevTab); + if (prevTabSortKeyWithinZone.has_value()) + { + tabSortKeyWithinZone = prevTabSortKeyWithinZone.value() + 1; + } + } + + m_windowsByIndexSets[indexSet].push_back(window); + } + + SetTabSortKeyWithinZone(window, tabSortKeyWithinZone); +} + IFACEMETHODIMP_(bool) ZoneSet::CalculateZones(RECT workAreaRect, int zoneCount, int spacing) noexcept { diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h index 9d7705cb0c..de1bbfc635 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h @@ -64,8 +64,9 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the * current monitor desktop work area. * @param indexSet The set of zone indices within zone layout. + * @param suppressMove Whether we should just update the records or move window to the zone. */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) = 0; + IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet, bool suppressMove = false) = 0; /** * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow), based on zone index numbers, * not their on-screen position. @@ -119,6 +120,21 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : */ IFACEMETHOD_(void, MoveWindowIntoZoneByPoint) (HWND window, HWND workAreaWindow, POINT ptClient) = 0; + /** + * Dismiss window from zone. + * + * @param window Handle of window which should be dismissed from zone. + */ + IFACEMETHOD_(void, DismissWindow) + (HWND window) = 0; + /** + * Cycle through tabs in the zone that the window is in. + * + * @param window Handle of window which is cycled from (the current tab). + * @param reverse Whether to cycle in reverse order (to the previous tab) or to move to the next tab. + */ + IFACEMETHOD_(void, CycleTabs) + (HWND window, bool reverse) = 0; /** * Calculate zone coordinates within zone layout based on number of zones and spacing. * diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 12148b04b5..8cb3619bcb 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -52,7 +52,10 @@ #define ZoneBorderColorKey "ZoneBorderColor" #define ZoneHighlightColorKey "ZoneHighlightColor" #define ZoneHighlightOpacityKey "ZoneHighlightOpacity" -#define HotkeyKey "Hotkey" +#define EditorHotkeyKey "EditorHotkey" +#define WindowSwitchingToggleKey "WindowSwitchingToggle" +#define NextTabHotkey "NextTabHotkey" +#define PrevTabHotkey "PrevTabHotkey" #define ExcludedAppsCountKey "ExcludedAppsCount" #define KeyboardValueKey "KeyboardValue" #define ActiveSetKey "ActiveSet" @@ -244,15 +247,21 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } +static std::wstring HotKeyToString(const PowerToysSettings::HotkeyObject& hotkey) +{ + return L"alt:" + std::to_wstring(hotkey.alt_pressed()) + + L", ctrl:" + std::to_wstring(hotkey.ctrl_pressed()) + + L", shift:" + std::to_wstring(hotkey.shift_pressed()) + + L", win:" + std::to_wstring(hotkey.win_pressed()) + + L", code:" + std::to_wstring(hotkey.get_code()) + + L", keyFromCode:" + hotkey.get_key(); +} + void Trace::SettingsTelemetry(const Settings& settings) noexcept { - const auto& editorHotkey = settings.editorHotkey; - std::wstring hotkeyStr = L"alt:" + std::to_wstring(editorHotkey.alt_pressed()) - + L", ctrl:" + std::to_wstring(editorHotkey.ctrl_pressed()) - + L", shift:" + std::to_wstring(editorHotkey.shift_pressed()) - + L", win:" + std::to_wstring(editorHotkey.win_pressed()) - + L", code:" + std::to_wstring(editorHotkey.get_code()) - + L", keyFromCode:" + editorHotkey.get_key(); + auto editorHotkeyStr = HotKeyToString(settings.editorHotkey); + auto nextTabHotkeyStr = HotKeyToString(settings.nextTabHotkey); + auto prevTabHotkeyStr = HotKeyToString(settings.prevTabHotkey); TraceLoggingWrite( g_hProvider, @@ -281,7 +290,10 @@ void Trace::SettingsTelemetry(const Settings& settings) noexcept TraceLoggingWideString(settings.zoneHighlightColor.c_str(), ZoneHighlightColorKey), TraceLoggingInt32(settings.zoneHighlightOpacity, ZoneHighlightOpacityKey), TraceLoggingInt32((int)settings.overlappingZonesAlgorithm, OverlappingZonesAlgorithmKey), - TraceLoggingWideString(hotkeyStr.c_str(), HotkeyKey), + TraceLoggingWideString(editorHotkeyStr.c_str(), EditorHotkeyKey), + TraceLoggingBoolean(settings.windowSwitching, WindowSwitchingToggleKey), + TraceLoggingWideString(nextTabHotkeyStr.c_str(), NextTabHotkey), + TraceLoggingWideString(prevTabHotkeyStr.c_str(), PrevTabHotkey), TraceLoggingInt32(static_cast(settings.excludedAppsArray.size()), ExcludedAppsCountKey)); } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp index 80c6b7d9aa..9228245268 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp @@ -70,6 +70,9 @@ namespace FancyZonesUnitTests PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests"); ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching); + ptSettings.add_hotkey(L"fancyzones_nextTab_hotkey", IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, settings.nextTabHotkey); + ptSettings.add_hotkey(L"fancyzones_prevTab_hotkey", IDS_SETTING_PREV_TAB_HOTKEY_LABEL, settings.prevTabHotkey); ptSettings.add_bool_toggle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); ptSettings.add_bool_toggle(L"fancyzones_mouseSwitch", IDS_SETTING_DESCRIPTION_MOUSESWITCH, settings.mouseSwitch); ptSettings.add_bool_toggle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp index 0e13a0ca69..3e6b4c530a 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp @@ -41,6 +41,7 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.showZonesOnAllMonitors, actual.showZonesOnAllMonitors); Assert::AreEqual(expected.spanZonesAcrossMonitors, actual.spanZonesAcrossMonitors); Assert::AreEqual(expected.makeDraggedWindowTransparent, actual.makeDraggedWindowTransparent); + Assert::AreEqual(expected.windowSwitching, actual.windowSwitching); Assert::AreEqual(expected.zoneColor.c_str(), actual.zoneColor.c_str()); Assert::AreEqual(expected.zoneBorderColor.c_str(), actual.zoneBorderColor.c_str()); Assert::AreEqual(expected.zoneHighlightColor.c_str(), actual.zoneHighlightColor.c_str()); @@ -53,6 +54,8 @@ namespace FancyZonesUnitTests } compareHotkeyObjects(expected.editorHotkey, actual.editorHotkey); + compareHotkeyObjects(expected.nextTabHotkey, actual.nextTabHotkey); + compareHotkeyObjects(expected.prevTabHotkey, actual.prevTabHotkey); } TEST_CLASS (FancyZonesSettingsCreationUnitTest) @@ -62,7 +65,6 @@ namespace FancyZonesUnitTests PCWSTR m_moduleKey = L"FancyZonesUnitTests"; std::wstring m_tmpName; - const PowerToysSettings::HotkeyObject m_defaultHotkeyObject = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3); const Settings m_defaultSettings; TEST_METHOD_INITIALIZE(Init) @@ -128,6 +130,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -168,6 +173,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -202,6 +210,8 @@ namespace FancyZonesUnitTests .zoneHighlightColor = L"#00FFD7", .zoneHighlightOpacity = 45, .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_NEXT), + .prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_PRIOR), .excludedApps = L"app", .excludedAppsArray = { L"APP" }, }; @@ -212,6 +222,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -246,6 +259,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -281,6 +297,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneColor", expected.zoneColor); values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -354,6 +373,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.save_to_settings_file(); @@ -416,6 +438,9 @@ namespace FancyZonesUnitTests IDS_SETTING_LAUNCH_EDITOR_BUTTON, IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching); + ptSettings.add_hotkey(L"fancyzones_nextTab_hotkey", IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, settings.nextTabHotkey); + ptSettings.add_hotkey(L"fancyzones_prevTab_hotkey", IDS_SETTING_PREV_TAB_HOTKEY_LABEL, settings.prevTabHotkey); ptSettings.add_bool_toggle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); ptSettings.add_bool_toggle(L"fancyzones_mouseSwitch", IDS_SETTING_DESCRIPTION_MOUSESWITCH, settings.mouseSwitch); ptSettings.add_bool_toggle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); @@ -517,6 +542,8 @@ namespace FancyZonesUnitTests .zoneHighlightColor = L"#00AABB", .zoneHighlightOpacity = 45, .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), + .nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_NEXT), + .prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_PRIOR), .excludedApps = L"app\r\napp2", .excludedAppsArray = { L"APP", L"APP2" }, }; diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs index bb5caa0afe..f873c4ff52 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs @@ -10,7 +10,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library public class FZConfigProperties { // in reality, this file needs to be kept in sync currently with src\modules\fancyzones\lib\Settings.h - public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, false, false, true, 0xc0); + public const int VkOem3 = 0xc0; + public const int VkNext = 0x22; + public const int VkPrior = 0x21; + + public static readonly HotkeySettings DefaultEditorHotkeyValue = new HotkeySettings(true, false, false, true, VkOem3); + public static readonly HotkeySettings DefaultNextTabHotkeyValue = new HotkeySettings(true, false, false, false, VkNext); + public static readonly HotkeySettings DefaultPrevTabHotkeyValue = new HotkeySettings(true, false, false, false, VkPrior); public FZConfigProperties() { @@ -32,7 +38,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library FancyzonesSpanZonesAcrossMonitors = new BoolProperty(); FancyzonesZoneHighlightColor = new StringProperty(ConfigDefaults.DefaultFancyZonesZoneHighlightColor); FancyzonesHighlightOpacity = new IntProperty(50); - FancyzonesEditorHotkey = new KeyboardKeysProperty(DefaultHotkeyValue); + FancyzonesEditorHotkey = new KeyboardKeysProperty(DefaultEditorHotkeyValue); + FancyzonesWindowSwitching = new BoolProperty(true); + FancyzonesNextTabHotkey = new KeyboardKeysProperty(DefaultNextTabHotkeyValue); + FancyzonesPrevTabHotkey = new KeyboardKeysProperty(DefaultPrevTabHotkeyValue); FancyzonesMakeDraggedWindowTransparent = new BoolProperty(); FancyzonesExcludedApps = new StringProperty(); FancyzonesInActiveColor = new StringProperty(ConfigDefaults.DefaultFancyZonesInActiveColor); @@ -99,6 +108,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("fancyzones_editor_hotkey")] public KeyboardKeysProperty FancyzonesEditorHotkey { get; set; } + [JsonPropertyName("fancyzones_windowSwitching")] + public BoolProperty FancyzonesWindowSwitching { get; set; } + + [JsonPropertyName("fancyzones_nextTab_hotkey")] + public KeyboardKeysProperty FancyzonesNextTabHotkey { get; set; } + + [JsonPropertyName("fancyzones_prevTab_hotkey")] + public KeyboardKeysProperty FancyzonesPrevTabHotkey { get; set; } + [JsonPropertyName("fancyzones_excluded_apps")] public StringProperty FancyzonesExcludedApps { get; set; } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index c990b1376e..7e163d1982 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -89,6 +89,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value; _excludedApps = Settings.Properties.FancyzonesExcludedApps.Value; EditorHotkey = Settings.Properties.FancyzonesEditorHotkey.Value; + _windowSwitching = Settings.Properties.FancyzonesWindowSwitching.Value; + NextTabHotkey = Settings.Properties.FancyzonesNextTabHotkey.Value; + PrevTabHotkey = Settings.Properties.FancyzonesPrevTabHotkey.Value; // set the callback functions value to hangle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; @@ -127,6 +130,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private int _highlightOpacity; private string _excludedApps; private HotkeySettings _editorHotkey; + private bool _windowSwitching; + private HotkeySettings _nextTabHotkey; + private HotkeySettings _prevTabHotkey; private string _zoneInActiveColor; private string _zoneBorderColor; private string _zoneHighlightColor; @@ -152,6 +158,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(SnapHotkeysCategoryEnabled)); OnPropertyChanged(nameof(QuickSwitchEnabled)); + OnPropertyChanged(nameof(WindowSwitchingCategoryEnabled)); } } } @@ -172,6 +179,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool WindowSwitchingCategoryEnabled + { + get + { + return _isEnabled && _windowSwitching; + } + } + public bool ShiftDrag { get @@ -604,7 +619,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { if (value == null || value.IsEmpty()) { - _editorHotkey = FZConfigProperties.DefaultHotkeyValue; + _editorHotkey = FZConfigProperties.DefaultEditorHotkeyValue; } else { @@ -617,6 +632,78 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool WindowSwitching + { + get + { + return _windowSwitching; + } + + set + { + if (value != _windowSwitching) + { + _windowSwitching = value; + + Settings.Properties.FancyzonesWindowSwitching.Value = _windowSwitching; + NotifyPropertyChanged(); + OnPropertyChanged(nameof(WindowSwitchingCategoryEnabled)); + } + } + } + + public HotkeySettings NextTabHotkey + { + get + { + return _nextTabHotkey; + } + + set + { + if (value != _nextTabHotkey) + { + if (value == null || value.IsEmpty()) + { + _nextTabHotkey = FZConfigProperties.DefaultNextTabHotkeyValue; + } + else + { + _nextTabHotkey = value; + } + + Settings.Properties.FancyzonesNextTabHotkey.Value = _nextTabHotkey; + NotifyPropertyChanged(); + } + } + } + + public HotkeySettings PrevTabHotkey + { + get + { + return _prevTabHotkey; + } + + set + { + if (value != _prevTabHotkey) + { + if (value == null || value.IsEmpty()) + { + _prevTabHotkey = FZConfigProperties.DefaultPrevTabHotkeyValue; + } + else + { + _prevTabHotkey = value; + } + + Settings.Properties.FancyzonesPrevTabHotkey.Value = _prevTabHotkey; + NotifyPropertyChanged(); + } + } + } + public string ExcludedApps { get diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs index 19895b3bce..18be72bac4 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs @@ -51,6 +51,9 @@ namespace ViewModelTests Assert.AreEqual(originalSettings.Properties.FancyzonesBorderColor.Value, viewModel.ZoneBorderColor); Assert.AreEqual(originalSettings.Properties.FancyzonesDisplayChangeMoveWindows.Value, viewModel.DisplayChangeMoveWindows); Assert.AreEqual(originalSettings.Properties.FancyzonesEditorHotkey.Value.ToString(), viewModel.EditorHotkey.ToString()); + Assert.AreEqual(originalSettings.Properties.FancyzonesWindowSwitching.Value, viewModel.WindowSwitching); + Assert.AreEqual(originalSettings.Properties.FancyzonesNextTabHotkey.Value.ToString(), viewModel.NextTabHotkey.ToString()); + Assert.AreEqual(originalSettings.Properties.FancyzonesPrevTabHotkey.Value.ToString(), viewModel.PrevTabHotkey.ToString()); Assert.AreEqual(originalSettings.Properties.FancyzonesExcludedApps.Value, viewModel.ExcludedApps); Assert.AreEqual(originalSettings.Properties.FancyzonesHighlightOpacity.Value, viewModel.HighlightOpacity); Assert.AreEqual(originalSettings.Properties.FancyzonesInActiveColor.Value, viewModel.ZoneInActiveColor); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index d43d39859f..252a8fa8c8 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -449,6 +449,24 @@ Open layout editor Shortcut to launch the FancyZones layout editor application + + + Window switching + + + Shortcuts for switching between windows in the current zone + + + Next window + + + Shortcut for switching to the next window in the current zone + + + Previous window + + + Shortcut for switching to the previous window in the current zone Shortcut setting diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index 13f7c8e283..9a83f979b6 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -148,6 +148,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + +