diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 436038accde7..8a4cb9032185 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -714,6 +714,7 @@ finalizer findstr FIXEDFILEINFO FLASHZONES +FLASHZONESONQUICKSWITCH Fle fluentui flyout @@ -1685,6 +1686,7 @@ Queryable QUERYENDSESSION QUERYOPEN QUEUESYNC +QUICKLAYOUTSWITCH qwertyuiopasdfghjklzxcvbnm qword qwrtyuiopsghjklzxvnm @@ -2095,6 +2097,7 @@ Timeline timeunion timeutil titlecase +TLayout tlb tlbimp tmp diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml index 737c8fa13006..d80e528727e1 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml @@ -284,6 +284,7 @@ + - - + - - + + + + @@ -495,6 +498,18 @@ SpinButtonPlacementMode="Compact" HorizontalAlignment="Left" AutomationProperties.LabeledBy="{Binding ElementName=sensitivityRadiusValue}" /> + + + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 11981ee22cf8..5fcba57db3ea 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -16,6 +17,8 @@ protected LayoutModel() { _guid = Guid.NewGuid(); Type = LayoutType.Custom; + + MainWindowSettingsModel.QuickKeys.PropertyChanged += QuickSwitchKeys_PropertyChanged; } protected LayoutModel(string name) @@ -48,6 +51,9 @@ protected LayoutModel(LayoutModel other) _isApplied = other._isApplied; _sensitivityRadius = other._sensitivityRadius; _zoneCount = other._zoneCount; + _quickKey = other._quickKey; + + MainWindowSettingsModel.QuickKeys.PropertyChanged += QuickSwitchKeys_PropertyChanged; } // Name - the display name for this layout model - is also used as the key in the registry @@ -158,6 +164,55 @@ public int SensitivityRadius private int _sensitivityRadius = LayoutSettings.DefaultSensitivityRadius; + public List QuickKeysAvailable + { + get + { + List result = new List(); + foreach (var pair in MainWindowSettingsModel.QuickKeys.SelectedKeys) + { + if (pair.Value == string.Empty || pair.Value == Uuid) + { + result.Add(pair.Key); + } + } + + return result; + } + } + + public string QuickKey + { + get + { + return _quickKey == -1 ? Properties.Resources.Quick_Key_None : _quickKey.ToString(); + } + + set + { + string none = Properties.Resources.Quick_Key_None; + var intValue = value == none ? -1 : int.Parse(value); + if (intValue != _quickKey) + { + string prev = _quickKey == -1 ? none : _quickKey.ToString(); + _quickKey = intValue; + + if (intValue != -1) + { + MainWindowSettingsModel.QuickKeys.SelectKey(value, Uuid); + } + else + { + MainWindowSettingsModel.QuickKeys.FreeKey(prev); + } + + FirePropertyChanged(nameof(QuickKey)); + } + } + } + + private int _quickKey = -1; + // TemplateZoneCount - number of zones selected in the picker window for template layouts public int TemplateZoneCount { @@ -200,6 +255,11 @@ protected virtual void FirePropertyChanged([CallerMemberName] string propertyNam // Removes this Layout from the registry and the loaded CustomModels list public void Delete() { + if (_quickKey != -1) + { + MainWindowSettingsModel.QuickKeys.FreeKey(QuickKey); + } + var customModels = MainWindowSettingsModel.CustomModels; int i = customModels.IndexOf(this); if (i != -1) @@ -241,5 +301,17 @@ public void Persist() { PersistData(); } + + private void QuickSwitchKeys_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + foreach (var pair in MainWindowSettingsModel.QuickKeys.SelectedKeys) + { + if (pair.Value == Uuid) + { + QuickKey = pair.Key.ToString(); + break; + } + } + } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs index d9a51bc0f658..2d514449ecc9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs @@ -6,7 +6,7 @@ namespace FancyZonesEditor.Models { public enum LayoutType { - Blank = -1, + Blank = 0, Focus, Columns, Rows, diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs index a23e1d8fd8c3..794575d7f69b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs @@ -23,13 +23,6 @@ private enum DeviceIdParts VirtualDesktopId, } - private readonly CanvasLayoutModel _blankModel; - private readonly CanvasLayoutModel _focusModel; - private readonly GridLayoutModel _rowsModel; - private readonly GridLayoutModel _columnsModel; - private readonly GridLayoutModel _gridModel; - private readonly GridLayoutModel _priorityGridModel; - // Non-localizable strings public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones"; public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath; @@ -53,40 +46,40 @@ public bool IsCustomLayoutActive public MainWindowSettingsModel() { // Initialize default layout models: Blank, Focus, Columns, Rows, Grid, and PriorityGrid - _blankModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Blank, LayoutType.Blank) + var blankModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Blank, LayoutType.Blank) { TemplateZoneCount = 0, SensitivityRadius = 0, }; - DefaultModels.Add(_blankModel); + DefaultModels.Insert((int)LayoutType.Blank, blankModel); - _focusModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Focus, LayoutType.Focus); - _focusModel.InitTemplateZones(); - DefaultModels.Add(_focusModel); + var focusModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Focus, LayoutType.Focus); + focusModel.InitTemplateZones(); + DefaultModels.Insert((int)LayoutType.Focus, focusModel); - _columnsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Columns, LayoutType.Columns) + var columnsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Columns, LayoutType.Columns) { Rows = 1, RowPercents = new List(1) { GridLayoutModel.GridMultiplier }, }; - _columnsModel.InitTemplateZones(); - DefaultModels.Add(_columnsModel); + columnsModel.InitTemplateZones(); + DefaultModels.Insert((int)LayoutType.Columns, columnsModel); - _rowsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Rows, LayoutType.Rows) + var rowsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Rows, LayoutType.Rows) { Columns = 1, ColumnPercents = new List(1) { GridLayoutModel.GridMultiplier }, }; - _rowsModel.InitTemplateZones(); - DefaultModels.Add(_rowsModel); + rowsModel.InitTemplateZones(); + DefaultModels.Insert((int)LayoutType.Rows, rowsModel); - _gridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Grid, LayoutType.Grid); - _gridModel.InitTemplateZones(); - DefaultModels.Add(_gridModel); + var gridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Grid, LayoutType.Grid); + gridModel.InitTemplateZones(); + DefaultModels.Insert((int)LayoutType.Grid, gridModel); - _priorityGridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Priority_Grid, LayoutType.PriorityGrid); - _priorityGridModel.InitTemplateZones(); - DefaultModels.Add(_priorityGridModel); + var priorityGridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Priority_Grid, LayoutType.PriorityGrid); + priorityGridModel.InitTemplateZones(); + DefaultModels.Insert((int)LayoutType.PriorityGrid, priorityGridModel); } // IsShiftKeyPressed - is the shift key currently being held down @@ -133,7 +126,7 @@ public LayoutModel BlankModel { get { - return _blankModel; + return DefaultModels[(int)LayoutType.Blank]; } } @@ -149,6 +142,8 @@ public static ObservableCollection CustomModels private static ObservableCollection _customModels = new ObservableCollection(); + public static QuickKeysModel QuickKeys { get; } = new QuickKeysModel(); + public LayoutModel SelectedModel { get @@ -202,7 +197,7 @@ public LayoutModel UpdateSelectedLayoutModel() { foreach (LayoutModel model in CustomModels) { - if ("{" + model.Guid.ToString().ToUpperInvariant() + "}" == currentApplied.ZonesetUuid.ToUpperInvariant()) + if (model.Uuid == currentApplied.ZonesetUuid.ToUpperInvariant()) { // found match foundModel = model; @@ -234,7 +229,7 @@ public LayoutModel UpdateSelectedLayoutModel() if (foundModel == null) { - foundModel = _priorityGridModel; + foundModel = DefaultModels[(int)LayoutType.PriorityGrid]; } SetSelectedModel(foundModel); @@ -255,6 +250,7 @@ public void RestoreSelectedModel(LayoutModel model) SelectedModel.IsSelected = model.IsSelected; SelectedModel.IsApplied = model.IsApplied; SelectedModel.Name = model.Name; + SelectedModel.QuickKey = model.QuickKey; if (model is GridLayoutModel grid) { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/QuickKeysModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/QuickKeysModel.cs new file mode 100644 index 000000000000..e1fc706b190e --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/QuickKeysModel.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace FancyZonesEditor.Models +{ + public class QuickKeysModel : INotifyPropertyChanged + { + public SortedDictionary SelectedKeys { get; } = new SortedDictionary() + { + { Properties.Resources.Quick_Key_None, string.Empty }, + { "0", string.Empty }, + { "1", string.Empty }, + { "2", string.Empty }, + { "3", string.Empty }, + { "4", string.Empty }, + { "5", string.Empty }, + { "6", string.Empty }, + { "7", string.Empty }, + { "8", string.Empty }, + { "9", string.Empty }, + }; + + public QuickKeysModel() + { + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void FreeKey(string key) + { + if (SelectedKeys.ContainsKey(key)) + { + SelectedKeys[key] = string.Empty; + FirePropertyChanged(); + } + } + + public bool SelectKey(string key, string uuid) + { + if (!SelectedKeys.ContainsKey(key)) + { + return false; + } + + if (SelectedKeys[key] == uuid) + { + return true; + } + + // clean previous value + foreach (var pair in SelectedKeys) + { + if (pair.Value == uuid) + { + SelectedKeys[pair.Key] = string.Empty; + break; + } + } + + SelectedKeys[key] = uuid; + FirePropertyChanged(); + return true; + } + + public void CleanUp() + { + var keys = SelectedKeys.Keys.ToList(); + foreach (var key in keys) + { + SelectedKeys[key] = string.Empty; + } + + FirePropertyChanged(); + } + + protected virtual void FirePropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs index 4c2b44636692..65f423c18433 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs @@ -555,6 +555,24 @@ public static string NumberOfZones { } } + /// + /// Looks up a localized string similar to None. + /// + public static string Quick_Key_None { + get { + return ResourceManager.GetString("Quick_Key_None", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select a key to quickly apply the layout (Win + Ctrl + Alt + key). + /// + public static string QuickKey_Select { + get { + return ResourceManager.GetString("QuickKey_Select", resourceCulture); + } + } + /// /// Looks up a localized string similar to Reset layout. /// diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx index 9a08373776fd..122af404937b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx @@ -346,4 +346,10 @@ Splitter: Title for concept: A segmenter visual for splitting one item into two. This would be the vertical line + + Select a key to quickly apply the layout (Win + Ctrl + Alt + key) + + + None + \ No newline at end of file diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs index 816308b365a9..439577e0f18d 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs @@ -57,6 +57,7 @@ private enum CmdArgs MonitorTop, } + // parsing cmd args private struct NativeMonitorData { public string MonitorId { get; set; } @@ -87,6 +88,7 @@ public override string ToString() } } + // zones-settings: devices private struct DeviceWrapper { public struct ActiveZoneSetWrapper @@ -109,6 +111,7 @@ public struct ActiveZoneSetWrapper public int EditorSensitivityRadius { get; set; } } + // zones-settings: custom-zone-sets private class CanvasInfoWrapper { public struct CanvasZoneWrapper @@ -131,6 +134,7 @@ public struct CanvasZoneWrapper public int SensitivityRadius { get; set; } = LayoutSettings.DefaultSensitivityRadius; } + // zones-settings: custom-zone-sets private class GridInfoWrapper { public int Rows { get; set; } @@ -150,6 +154,7 @@ private class GridInfoWrapper public int SensitivityRadius { get; set; } = LayoutSettings.DefaultSensitivityRadius; } + // zones-settings: custom-zone-sets private struct CustomLayoutWrapper { public string Uuid { get; set; } @@ -161,6 +166,7 @@ private struct CustomLayoutWrapper public JsonElement Info { get; set; } // CanvasInfoWrapper or GridInfoWrapper } + // zones-settings: templates private struct TemplateLayoutWrapper { public string Type { get; set; } @@ -174,6 +180,15 @@ private struct TemplateLayoutWrapper public int SensitivityRadius { get; set; } } + // zones-settings: quick-layout-keys-wrapper + private struct QuickLayoutKeysWrapper + { + public int Key { get; set; } + + public string Uuid { get; set; } + } + + // zones-settings private struct ZoneSettingsWrapper { public List Devices { get; set; } @@ -181,6 +196,8 @@ private struct ZoneSettingsWrapper public List CustomZoneSets { get; set; } public List Templates { get; set; } + + public List QuickLayoutKeys { get; set; } } private struct EditorParams @@ -528,6 +545,7 @@ public ParsingResult ParseZoneSettings() bool devicesParsingResult = SetDevices(zoneSettings.Devices); bool customZonesParsingResult = SetCustomLayouts(zoneSettings.CustomZoneSets); bool templatesParsingResult = SetTemplateLayouts(zoneSettings.Templates); + bool quickLayoutSwitchKeysParsingResult = SetQuickLayoutSwitchKeys(zoneSettings.QuickLayoutKeys); if (!devicesParsingResult || !customZonesParsingResult) { @@ -549,6 +567,7 @@ public void SerializeZoneSettings() zoneSettings.Devices = new List(); zoneSettings.CustomZoneSets = new List(); zoneSettings.Templates = new List(); + zoneSettings.QuickLayoutKeys = new List(); // Serialize used devices foreach (var monitor in App.Overlay.Monitors) @@ -685,6 +704,27 @@ public void SerializeZoneSettings() zoneSettings.Templates.Add(wrapper); } + // Serialize quick layout switch keys + foreach (var pair in MainWindowSettingsModel.QuickKeys.SelectedKeys) + { + if (pair.Value != string.Empty) + { + try + { + QuickLayoutKeysWrapper wrapper = new QuickLayoutKeysWrapper + { + Key = int.Parse(pair.Key), + Uuid = pair.Value, + }; + + zoneSettings.QuickLayoutKeys.Add(wrapper); + } + catch (Exception) + { + } + } + } + try { string jsonString = JsonSerializer.Serialize(zoneSettings, _options); @@ -781,7 +821,15 @@ private bool SetCustomLayouts(List customLayouts) zones.Add(new Int32Rect { X = (int)zone.X, Y = (int)zone.Y, Width = (int)zone.Width, Height = (int)zone.Height }); } - layout = new CanvasLayoutModel(zoneSet.Uuid, zoneSet.Name, LayoutType.Custom, zones, info.RefWidth, info.RefHeight); + try + { + layout = new CanvasLayoutModel(zoneSet.Uuid, zoneSet.Name, LayoutType.Custom, zones, info.RefWidth, info.RefHeight); + } + catch (Exception) + { + continue; + } + layout.SensitivityRadius = info.SensitivityRadius; } else if (zoneSet.Type == GridLayoutModel.ModelTypeID) @@ -797,7 +845,15 @@ private bool SetCustomLayouts(List customLayouts) } } - layout = new GridLayoutModel(zoneSet.Uuid, zoneSet.Name, LayoutType.Custom, info.Rows, info.Columns, info.RowsPercentage, info.ColumnsPercentage, cells); + try + { + layout = new GridLayoutModel(zoneSet.Uuid, zoneSet.Name, LayoutType.Custom, info.Rows, info.Columns, info.RowsPercentage, info.ColumnsPercentage, cells); + } + catch (Exception) + { + continue; + } + layout.SensitivityRadius = info.SensitivityRadius; (layout as GridLayoutModel).ShowSpacing = info.ShowSpacing; (layout as GridLayoutModel).Spacing = info.Spacing; @@ -823,24 +879,35 @@ private bool SetTemplateLayouts(List templateLayouts) foreach (var wrapper in templateLayouts) { - var type = JsonTagToLayoutType(wrapper.Type); + LayoutType type = JsonTagToLayoutType(wrapper.Type); + LayoutModel layout = MainWindowSettingsModel.DefaultModels[(int)type]; + + layout.SensitivityRadius = wrapper.SensitivityRadius; + layout.TemplateZoneCount = wrapper.ZoneCount; - foreach (var layout in MainWindowSettingsModel.DefaultModels) + if (layout is GridLayoutModel grid) { - if (layout.Type == type) - { - layout.SensitivityRadius = wrapper.SensitivityRadius; - layout.TemplateZoneCount = wrapper.ZoneCount; + grid.ShowSpacing = wrapper.ShowSpacing; + grid.Spacing = wrapper.Spacing; + } - if (layout is GridLayoutModel grid) - { - grid.ShowSpacing = wrapper.ShowSpacing; - grid.Spacing = wrapper.Spacing; - } + layout.InitTemplateZones(); + } - layout.InitTemplateZones(); - } - } + return true; + } + + private bool SetQuickLayoutSwitchKeys(List quickSwitchKeys) + { + if (quickSwitchKeys == null) + { + return false; + } + + MainWindowSettingsModel.QuickKeys.CleanUp(); + foreach (var wrapper in quickSwitchKeys) + { + MainWindowSettingsModel.QuickKeys.SelectKey(wrapper.Key.ToString(), wrapper.Uuid); } return true; diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 7655823698ab..8d8880805e59 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -34,6 +34,30 @@ enum class DisplayChangeType namespace { constexpr int CUSTOM_POSITIONING_LEFT_TOP_PADDING = 16; + + struct require_read_lock + { + template + require_read_lock(const std::shared_lock& lock) + { + lock; + } + + template + require_read_lock(const std::unique_lock& lock) + { + lock; + } + }; + + struct require_write_lock + { + template + require_write_lock(const std::unique_lock& lock) + { + lock; + } + }; } // Non-localizable strings @@ -184,40 +208,16 @@ struct FancyZones : public winrt::implements - require_read_lock(const std::shared_lock& lock) - { - lock; - } - - template - require_read_lock(const std::unique_lock& lock) - { - lock; - } - }; - - struct require_write_lock - { - template - require_write_lock(const std::unique_lock& lock) - { - lock; - } - }; - void UpdateZoneWindows() noexcept; - void UpdateWindowsPositions() noexcept; - void CycleActiveZoneSet(DWORD vkCode) noexcept; + void UpdateZoneWindows(require_write_lock) noexcept; + void UpdateWindowsPositions(require_write_lock) noexcept; bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkey(DWORD vkCode) noexcept; @@ -232,12 +232,15 @@ struct FancyZones : public winrt::implements, std::vector> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, bool isPrimaryMonitor) noexcept; void MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const std::vector& zoneIndexSet) noexcept; - void OnEditorExitEvent() noexcept; - void UpdateZoneSets() noexcept; + void OnEditorExitEvent(require_write_lock) noexcept; + void UpdateZoneSets(require_write_lock) noexcept; bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept; + void ApplyQuickLayout(int key) noexcept; + void FlashZones(require_write_lock) noexcept; std::vector> GetRawMonitorData() noexcept; std::vector GetMonitorsSorted() noexcept; + HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; const HINSTANCE m_hinstance{}; @@ -266,6 +269,7 @@ struct FancyZones : public winrt::implementsvkCode >= '0') && (info->vkCode <= '9')) - // { - // // Win+Ctrl+Number will cycle through ZoneSets - // Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/); - // CycleActiveZoneSet(info->vkCode); - // return true; - // } - // } - // else if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT) || (info->vkCode == VK_UP) || (info->vkCode == VK_DOWN)) { if (ShouldProcessSnapHotkey(info->vkCode)) @@ -576,14 +569,34 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept } } } - // Temporarily disable Win+Ctrl+Number functionality - //else if (m_inMoveSize && (info->vkCode >= '0') && (info->vkCode <= '9')) - //{ - // // This allows you to cycle through ZoneSets while dragging a window - // Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/); - // CycleActiveZoneSet(info->vkCode); - // return false; - //} + + if (m_settings->GetSettings()->quickLayoutSwitch) + { + int digitPressed = -1; + if ('0' <= info->vkCode && info->vkCode <= '9') + { + digitPressed = info->vkCode - '0'; + } + else if (VK_NUMPAD0 <= info->vkCode && info->vkCode <= VK_NUMPAD9) + { + digitPressed = info->vkCode - VK_NUMPAD0; + } + + bool dragging = m_windowMoveHandler.InMoveSize(); + bool changeLayoutWhileNotDragging = !dragging && !shift && win && ctrl && alt && digitPressed != -1; + bool changeLayoutWhileDragging = dragging && digitPressed != -1; + + if (changeLayoutWhileNotDragging || changeLayoutWhileDragging) + { + auto quickKeysMap = FancyZonesDataInstance().GetLayoutQuickKeys(); + if (std::any_of(quickKeysMap.begin(), quickKeysMap.end(), [=](auto item) { return item.second == digitPressed; })) + { + PostMessageW(m_window, WM_PRIV_QUICK_LAYOUT_KEY, 0, static_cast(digitPressed)); + Trace::FancyZones::QuickLayoutSwitched(changeLayoutWhileNotDragging); + return true; + } + } + } if (m_windowMoveHandler.IsDragEnabled() && shift) { @@ -755,13 +768,16 @@ void FancyZones::ToggleEditor() noexcept void FancyZones::SettingsChanged() noexcept { + _TRACER_; + std::unique_lock writeLock(m_lock); + // Update the hotkey UnregisterHotKey(m_window, 1); RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); // Needed if we toggled spanZonesAcrossMonitors m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::Initialization); + OnDisplayChange(DisplayChangeType::Initialization, writeLock); } // IZoneWindowHost @@ -770,7 +786,8 @@ FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept { if (m_settings->GetSettings()->zoneSetChange_moveWindows) { - UpdateWindowsPositions(); + std::unique_lock writeLock(m_lock); + UpdateWindowsPositions(writeLock); } } @@ -793,8 +810,9 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa { // Changes in taskbar position resulted in different size of work area. // Invalidate cached work-areas so they can be recreated with latest information. + std::unique_lock writeLock(m_lock); m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::WorkArea); + OnDisplayChange(DisplayChangeType::WorkArea, writeLock); } } break; @@ -802,8 +820,9 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa case WM_DISPLAYCHANGE: { // Display resolution changed. Invalidate cached work-areas so they can be recreated with latest information. + std::unique_lock writeLock(m_lock); m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::DisplayChange); + OnDisplayChange(DisplayChangeType::DisplayChange, writeLock); } break; @@ -818,11 +837,13 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa } else if (message == WM_PRIV_VD_INIT) { - OnDisplayChange(DisplayChangeType::Initialization); + std::unique_lock writeLock(m_lock); + OnDisplayChange(DisplayChangeType::Initialization, writeLock); } else if (message == WM_PRIV_VD_SWITCH) { - OnDisplayChange(DisplayChangeType::VirtualDesktop); + std::unique_lock writeLock(m_lock); + OnDisplayChange(DisplayChangeType::VirtualDesktop, writeLock); } else if (message == WM_PRIV_VD_UPDATE) { @@ -836,7 +857,8 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa { if (lparam == static_cast(EditorExitKind::Exit)) { - OnEditorExitEvent(); + std::unique_lock writeLock(m_lock); + OnEditorExitEvent(writeLock); } { @@ -873,7 +895,12 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa else if (message == WM_PRIV_FILE_UPDATE) { FancyZonesDataInstance().LoadFancyZonesData(); - UpdateZoneSets(); + std::unique_lock writeLock(m_lock); + UpdateZoneSets(writeLock); + } + else if (message == WM_PRIV_QUICK_LAYOUT_KEY) + { + ApplyQuickLayout(static_cast(lparam)); } else { @@ -885,7 +912,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa return 0; } -void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept +void FancyZones::OnDisplayChange(DisplayChangeType changeType, require_write_lock lock) noexcept { _TRACER_; if (changeType == DisplayChangeType::VirtualDesktop || @@ -912,22 +939,20 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept } } - UpdateZoneWindows(); + UpdateZoneWindows(lock); if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) { if (m_settings->GetSettings()->displayChange_moveWindows) { - UpdateWindowsPositions(); + UpdateWindowsPositions(lock); } } } -void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId) noexcept +void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId, require_write_lock) noexcept { _TRACER_; - std::unique_lock writeLock(m_lock); - if (m_workAreaHandler.IsNewWorkArea(m_currentDesktopId, monitor)) { wil::unique_cotaskmem_string virtualDesktopId; @@ -974,7 +999,7 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, DefWindowProc(window, message, wparam, lparam); } -void FancyZones::UpdateZoneWindows() noexcept +void FancyZones::UpdateZoneWindows(require_write_lock lock) noexcept { // Mapping between display device name and device index (operating system identifies each display device with an index value). std::unordered_map displayDeviceIdxMap; @@ -982,6 +1007,7 @@ void FancyZones::UpdateZoneWindows() noexcept { FancyZones* fancyZones; std::unordered_map* displayDeviceIdx; + require_write_lock lock; }; auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { @@ -993,27 +1019,25 @@ void FancyZones::UpdateZoneWindows() noexcept FancyZones* fancyZones = params->fancyZones; std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(mi.szDevice, displayDeviceIdxMap); - fancyZones->AddZoneWindow(monitor, deviceId); + fancyZones->AddZoneWindow(monitor, deviceId, params->lock); } return TRUE; }; if (m_settings->GetSettings()->spanZonesAcrossMonitors) { - AddZoneWindow(nullptr, {}); + AddZoneWindow(nullptr, {}, lock); } else { - capture capture{ this, &displayDeviceIdxMap }; + capture capture{ this, &displayDeviceIdxMap, lock }; EnumDisplayMonitors(nullptr, nullptr, callback, reinterpret_cast(&capture)); } } -void FancyZones::UpdateWindowsPositions() noexcept +void FancyZones::UpdateWindowsPositions(require_write_lock) noexcept { - _TRACER_; auto callback = [](HWND window, LPARAM data) -> BOOL { - _TRACER_; size_t bitmask = reinterpret_cast(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)); if (bitmask != 0) @@ -1028,7 +1052,6 @@ void FancyZones::UpdateWindowsPositions() noexcept } auto strongThis = reinterpret_cast(data); - std::unique_lock writeLock(strongThis->m_lock); auto zoneWindow = strongThis->m_workAreaHandler.GetWorkArea(window); if (zoneWindow) { @@ -1040,39 +1063,10 @@ void FancyZones::UpdateWindowsPositions() noexcept EnumWindows(callback, reinterpret_cast(this)); } -void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept -{ - _TRACER_; - auto window = GetForegroundWindow(); - if (FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray)) - { - const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - if (monitor) - { - std::shared_lock readLock(m_lock); - - auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); - if (zoneWindow) - { - zoneWindow->CycleActiveZoneSet(vkCode); - } - } - } -} - bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept { _TRACER_; - HMONITOR current; - - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - current = NULL; - } - else - { - current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - } + HMONITOR current = WorkAreaKeyFromWindow(window); std::vector monitorInfo = GetMonitorsSorted(); if (current && monitorInfo.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors) @@ -1130,16 +1124,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept { - HMONITOR current; - - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - current = NULL; - } - else - { - current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - } + HMONITOR current = WorkAreaKeyFromWindow(window); auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); @@ -1316,14 +1301,14 @@ bool FancyZones::IsSplashScreen(HWND window) return wcscmp(NonLocalizable::SplashClassName, className) == 0; } -void FancyZones::OnEditorExitEvent() noexcept +void FancyZones::OnEditorExitEvent(require_write_lock lock) noexcept { // Collect information about changes in zone layout after editor exited. FancyZonesDataInstance().LoadFancyZonesData(); - UpdateZoneSets(); + UpdateZoneSets(lock); } -void FancyZones::UpdateZoneSets() noexcept +void FancyZones::UpdateZoneSets(require_write_lock lock) noexcept { for (auto workArea : m_workAreaHandler.GetAllWorkAreas()) { @@ -1331,7 +1316,7 @@ void FancyZones::UpdateZoneSets() noexcept } if (m_settings->GetSettings()->zoneSetChange_moveWindows) { - UpdateWindowsPositions(); + UpdateWindowsPositions(lock); } } @@ -1340,15 +1325,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept auto window = GetForegroundWindow(); if (m_settings->GetSettings()->overrideSnapHotkeys && FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray)) { - HMONITOR monitor; - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - monitor = NULL; - } - else - { - monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - } + HMONITOR monitor = WorkAreaKeyFromWindow(window); auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); if (zoneWindow && zoneWindow->ActiveZoneSet() && zoneWindow->ActiveZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) @@ -1366,6 +1343,47 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept return false; } +void FancyZones::ApplyQuickLayout(int key) noexcept +{ + std::unique_lock writeLock(m_lock); + + std::wstring uuid; + for (auto [zoneUuid, hotkey] : FancyZonesDataInstance().GetLayoutQuickKeys()) + { + if (hotkey == key) + { + uuid = zoneUuid; + } + } + + auto workArea = m_workAreaHandler.GetWorkAreaFromCursor(m_currentDesktopId); + + // Find a custom zone set with this uuid and apply it + auto customZoneSets = FancyZonesDataInstance().GetCustomZoneSetsMap(); + + if (!customZoneSets.contains(uuid)) + { + return; + } + + FancyZonesDataTypes::ZoneSetData data{ .uuid = uuid, .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom }; + FancyZonesDataInstance().SetActiveZoneSet(workArea->UniqueId(), data); + FancyZonesDataInstance().SaveZoneSettings(); + UpdateZoneSets(writeLock); + FlashZones(writeLock); +} + +void FancyZones::FlashZones(require_write_lock) noexcept +{ + if (m_settings->GetSettings()->flashZonesOnQuickSwitch && !m_windowMoveHandler.IsDragEnabled()) + { + for (auto [monitor, workArea] : m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)) + { + workArea->FlashZones(); + } + } +} + std::vector FancyZones::GetMonitorsSorted() noexcept { std::shared_lock readLock(m_lock); @@ -1397,6 +1415,18 @@ std::vector> FancyZones::GetRawMonitorData() noexcept return monitorInfo; } +HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept +{ + if (m_settings->GetSettings()->spanZonesAcrossMonitors) + { + return NULL; + } + else + { + return MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + } +} + winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings, std::function disableCallback) noexcept diff --git a/src/modules/fancyzones/lib/FancyZonesData.cpp b/src/modules/fancyzones/lib/FancyZonesData.cpp index 487b8b862fed..c72524ace090 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.cpp +++ b/src/modules/fancyzones/lib/FancyZonesData.cpp @@ -511,10 +511,33 @@ bool FancyZonesData::SetAppLastZones(HWND window, const std::wstring& deviceId, void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const FancyZonesDataTypes::ZoneSetData& data) { std::scoped_lock lock{ dataLock }; - auto it = deviceInfoMap.find(deviceId); - if (it != deviceInfoMap.end()) + + auto deviceIt = deviceInfoMap.find(deviceId); + if (deviceIt == deviceInfoMap.end()) + { + return; + } + + deviceIt->second.activeZoneSet = data; + + // If the zone set is custom, we need to copy its properties to the device + auto zonesetIt = customZoneSetsMap.find(data.uuid); + if (zonesetIt != customZoneSetsMap.end()) { - it->second.activeZoneSet = data; + if (zonesetIt->second.type == FancyZonesDataTypes::CustomLayoutType::Grid) + { + auto layoutInfo = std::get(zonesetIt->second.info); + deviceIt->second.sensitivityRadius = layoutInfo.sensitivityRadius(); + deviceIt->second.showSpacing = layoutInfo.showSpacing(); + deviceIt->second.spacing = layoutInfo.spacing(); + deviceIt->second.zoneCount = layoutInfo.zoneCount(); + } + else if (zonesetIt->second.type == FancyZonesDataTypes::CustomLayoutType::Canvas) + { + auto layoutInfo = std::get(zonesetIt->second.info); + deviceIt->second.sensitivityRadius = layoutInfo.sensitivityRadius; + deviceIt->second.zoneCount = (int)layoutInfo.zones.size(); + } } } @@ -536,6 +559,7 @@ void FancyZonesData::LoadFancyZonesData() appZoneHistoryMap = JSONHelpers::ParseAppZoneHistory(fancyZonesDataJSON); deviceInfoMap = JSONHelpers::ParseDeviceInfos(fancyZonesDataJSON); customZoneSetsMap = JSONHelpers::ParseCustomZoneSets(fancyZonesDataJSON); + quickKeysMap = JSONHelpers::ParseQuickKeys(fancyZonesDataJSON); } } @@ -549,7 +573,7 @@ void FancyZonesData::SaveZoneSettings() const { _TRACER_; std::scoped_lock lock{ dataLock }; - JSONHelpers::SaveZoneSettings(zonesSettingsFileName, deviceInfoMap, customZoneSetsMap); + JSONHelpers::SaveZoneSettings(zonesSettingsFileName, deviceInfoMap, customZoneSetsMap, quickKeysMap); } void FancyZonesData::SaveAppZoneHistory() const diff --git a/src/modules/fancyzones/lib/FancyZonesData.h b/src/modules/fancyzones/lib/FancyZonesData.h index aea46093a912..34dc72b6fd8e 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.h +++ b/src/modules/fancyzones/lib/FancyZonesData.h @@ -48,6 +48,12 @@ class FancyZonesData const std::unordered_map>& GetAppZoneHistoryMap() const; + inline const JSONHelpers::TLayoutQuickKeysMap& GetLayoutQuickKeys() const + { + std::scoped_lock lock{ dataLock }; + return quickKeysMap; + } + inline const std::wstring& GetZonesSettingsFileName() const { return zonesSettingsFileName; @@ -121,6 +127,8 @@ class FancyZonesData JSONHelpers::TDeviceInfoMap deviceInfoMap{}; // Maps custom zoneset UUID to it's data JSONHelpers::TCustomZoneSetsMap customZoneSetsMap{}; + // Maps zoneset UUID with quick access keys + JSONHelpers::TLayoutQuickKeysMap quickKeysMap{}; std::wstring zonesSettingsFileName; std::wstring appZoneHistoryFileName; diff --git a/src/modules/fancyzones/lib/FancyZonesDataTypes.cpp b/src/modules/fancyzones/lib/FancyZonesDataTypes.cpp index eece88e7d30c..7c017d3319bd 100644 --- a/src/modules/fancyzones/lib/FancyZonesDataTypes.cpp +++ b/src/modules/fancyzones/lib/FancyZonesDataTypes.cpp @@ -112,4 +112,18 @@ namespace FancyZonesDataTypes cellRow.resize(m_columns, 0); } } + + int GridLayoutInfo::zoneCount() const + { + int high = 0; + for (const auto& row : m_cellChildMap) + { + for (int val : row) + { + high = max(high, val); + } + } + + return high + 1; + } } diff --git a/src/modules/fancyzones/lib/FancyZonesDataTypes.h b/src/modules/fancyzones/lib/FancyZonesDataTypes.h index fd262af15a85..6ab782f9c986 100644 --- a/src/modules/fancyzones/lib/FancyZonesDataTypes.h +++ b/src/modules/fancyzones/lib/FancyZonesDataTypes.h @@ -86,6 +86,8 @@ namespace FancyZonesDataTypes inline int spacing() const { return m_spacing; } inline int sensitivityRadius() const { return m_sensitivityRadius; } + int zoneCount() const; + int m_rows; int m_columns; std::vector m_rowsPercents; diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 3cd049910344..d6b8570e3337 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -35,6 +35,9 @@ namespace NonLocalizable const wchar_t HistoryStr[] = L"history"; const wchar_t InfoStr[] = L"info"; const wchar_t NameStr[] = L"name"; + const wchar_t QuickAccessKey[] = L"key"; + const wchar_t QuickAccessUuid[] = L"uuid"; + const wchar_t QuickLayoutKeys[] = L"quick-layout-keys"; const wchar_t RefHeightStr[] = L"ref-height"; const wchar_t RefWidthStr[] = L"ref-width"; const wchar_t RowsPercentageStr[] = L"rows-percentage"; @@ -471,6 +474,38 @@ namespace JSONHelpers } } + json::JsonObject LayoutQuickKeyJSON::ToJson(const LayoutQuickKeyJSON& layoutQuickKey) + { + json::JsonObject result{}; + + result.SetNamedValue(NonLocalizable::QuickAccessUuid, json::value(layoutQuickKey.layoutUuid)); + result.SetNamedValue(NonLocalizable::QuickAccessKey, json::value(layoutQuickKey.key)); + + return result; + } + + std::optional LayoutQuickKeyJSON::FromJson(const json::JsonObject& layoutQuickKey) + { + try + { + LayoutQuickKeyJSON result; + + result.layoutUuid = layoutQuickKey.GetNamedString(NonLocalizable::QuickAccessUuid); + if (!FancyZonesUtils::IsValidGuid(result.layoutUuid)) + { + return std::nullopt; + } + + result.key = static_cast(layoutQuickKey.GetNamedNumber(NonLocalizable::QuickAccessKey)); + + return result; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + json::JsonObject MonitorInfo::ToJson(const MonitorInfo& monitor) { json::JsonObject result{}; @@ -527,7 +562,7 @@ namespace JSONHelpers } } - void SaveZoneSettings(const std::wstring& zonesSettingsFileName, const TDeviceInfoMap& deviceInfoMap, const TCustomZoneSetsMap& customZoneSetsMap) + void SaveZoneSettings(const std::wstring& zonesSettingsFileName, const TDeviceInfoMap& deviceInfoMap, const TCustomZoneSetsMap& customZoneSetsMap, const TLayoutQuickKeysMap& quickKeysMap) { auto before = json::from_file(zonesSettingsFileName); @@ -549,6 +584,7 @@ namespace JSONHelpers root.SetNamedValue(NonLocalizable::DevicesStr, JSONHelpers::SerializeDeviceInfos(deviceInfoMap)); root.SetNamedValue(NonLocalizable::CustomZoneSetsStr, JSONHelpers::SerializeCustomZoneSets(customZoneSetsMap)); root.SetNamedValue(NonLocalizable::Templates, templates); + root.SetNamedValue(NonLocalizable::QuickLayoutKeys, JSONHelpers::SerializeQuickKeys(quickKeysMap)); if (!before.has_value() || before.value().Stringify() != root.Stringify()) { @@ -675,4 +711,40 @@ namespace JSONHelpers return customZoneSetsJSON; } + + TLayoutQuickKeysMap ParseQuickKeys(const json::JsonObject& fancyZonesDataJSON) + { + try + { + TLayoutQuickKeysMap quickKeysMap{}; + auto quickKeys = fancyZonesDataJSON.GetNamedArray(NonLocalizable::QuickLayoutKeys); + + for (uint32_t i = 0; i < quickKeys.Size(); ++i) + { + if (auto quickKey = LayoutQuickKeyJSON::FromJson(quickKeys.GetObjectAt(i)); quickKey.has_value()) + { + quickKeysMap[quickKey->layoutUuid] = std::move(quickKey->key); + } + } + + return std::move(quickKeysMap); + } + catch (const winrt::hresult_error& e) + { + Logger::error(L"Parsing quick keys error: {}", e.message()); + return {}; + } + } + + json::JsonArray SerializeQuickKeys(const TLayoutQuickKeysMap& quickKeysMap) + { + json::JsonArray quickKeysJSON{}; + + for (const auto& [uuid, key] : quickKeysMap) + { + quickKeysJSON.Append(LayoutQuickKeyJSON::ToJson(LayoutQuickKeyJSON{ uuid, key })); + } + + return quickKeysJSON; + } } \ No newline at end of file diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index eb933721e87d..52d3bc327cca 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -55,9 +55,19 @@ namespace JSONHelpers static std::optional FromJson(const json::JsonObject& device); }; + struct LayoutQuickKeyJSON + { + std::wstring layoutUuid; + int key; + + static json::JsonObject ToJson(const LayoutQuickKeyJSON& device); + static std::optional FromJson(const json::JsonObject& device); + }; + using TAppZoneHistoryMap = std::unordered_map>; using TDeviceInfoMap = std::unordered_map; using TCustomZoneSetsMap = std::unordered_map; + using TLayoutQuickKeysMap = std::unordered_map; struct MonitorInfo { @@ -81,7 +91,7 @@ namespace JSONHelpers json::JsonObject GetPersistFancyZonesJSON(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName); - void SaveZoneSettings(const std::wstring& zonesSettingsFileName, const TDeviceInfoMap& deviceInfoMap, const TCustomZoneSetsMap& customZoneSetsMap); + void SaveZoneSettings(const std::wstring& zonesSettingsFileName, const TDeviceInfoMap& deviceInfoMap, const TCustomZoneSetsMap& customZoneSetsMap, const TLayoutQuickKeysMap& quickKeysMap); void SaveAppZoneHistory(const std::wstring& appZoneHistoryFileName, const TAppZoneHistoryMap& appZoneHistoryMap); TAppZoneHistoryMap ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); @@ -92,4 +102,7 @@ namespace JSONHelpers TCustomZoneSetsMap ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON); json::JsonArray SerializeCustomZoneSets(const TCustomZoneSetsMap& customZoneSetsMap); + + TLayoutQuickKeysMap ParseQuickKeys(const json::JsonObject& fancyZonesDataJSON); + json::JsonArray SerializeQuickKeys(const TLayoutQuickKeysMap& quickKeysMap); } diff --git a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp b/src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp index 049cdae0ae9c..ebad94a9aeaf 100644 --- a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp +++ b/src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp @@ -17,6 +17,27 @@ winrt::com_ptr MonitorWorkAreaHandler::GetWorkArea(const GUID& desk return nullptr; } +winrt::com_ptr MonitorWorkAreaHandler::GetWorkAreaFromCursor(const GUID& desktopId) +{ + auto allMonitorsWorkArea = GetWorkArea(desktopId, NULL); + if (allMonitorsWorkArea) + { + // First, check if there's a work area spanning all monitors (signalled by the NULL monitor handle) + return allMonitorsWorkArea; + } + else + { + // Otherwise, look for the work area based on cursor position + POINT cursorPoint; + if (!GetCursorPos(&cursorPoint)) + { + return nullptr; + } + + return GetWorkArea(desktopId, MonitorFromPoint(cursorPoint, MONITOR_DEFAULTTONULL)); + } +} + winrt::com_ptr MonitorWorkAreaHandler::GetWorkArea(HWND window) { GUID desktopId{}; diff --git a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.h b/src/modules/fancyzones/lib/MonitorWorkAreaHandler.h index 10f9df7dd957..a876d62275bf 100644 --- a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.h +++ b/src/modules/fancyzones/lib/MonitorWorkAreaHandler.h @@ -29,6 +29,16 @@ class MonitorWorkAreaHandler */ winrt::com_ptr GetWorkArea(const GUID& desktopId, HMONITOR monitor); + /** + * Get work area based on virtual desktop id and the current cursor position. + * + * @param[in] desktopId Virtual desktop identifier. + * + * @returns Object representing single work area, interface to all actions available on work area + * (e.g. moving windows through zone layout specified for that work area). + */ + winrt::com_ptr GetWorkAreaFromCursor(const GUID& desktopId); + /** * Get work area on which specified window is located. * diff --git a/src/modules/fancyzones/lib/Resources.resx b/src/modules/fancyzones/lib/Resources.resx index 641609a08e17..f0dff6271a1d 100644 --- a/src/modules/fancyzones/lib/Resources.resx +++ b/src/modules/fancyzones/lib/Resources.resx @@ -247,4 +247,10 @@ Failed to save the FancyZones settings. Please retry again later, if the problem persists report the bug to "Report bug to" will have a URL after. FancyZone is a product name, keep as is. + + Enable quick layout switch + + + Flash zones when switching layout + \ No newline at end of file diff --git a/src/modules/fancyzones/lib/Settings.cpp b/src/modules/fancyzones/lib/Settings.cpp index 137d81357904..111a50c3170f 100644 --- a/src/modules/fancyzones/lib/Settings.cpp +++ b/src/modules/fancyzones/lib/Settings.cpp @@ -21,6 +21,8 @@ namespace NonLocalizable const wchar_t AppLastZoneMoveWindowsID[] = L"fancyzones_appLastZone_moveWindows"; const wchar_t OpenWindowOnActiveMonitorID[] = L"fancyzones_openWindowOnActiveMonitor"; const wchar_t RestoreSizeID[] = L"fancyzones_restoreSize"; + const wchar_t QuickLayoutSwitch[] = L"fancyzones_quickLayoutSwitch"; + const wchar_t FlashZonesOnQuickSwitch[] = L"fancyzones_flashZonesOnQuickSwitch"; const wchar_t UseCursorPosEditorStartupScreenID[] = L"use_cursorpos_editor_startupscreen"; const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors"; const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors"; @@ -80,7 +82,7 @@ struct FancyZonesSettings : winrt::implements MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept; - IFACEMETHODIMP_(void) - CycleActiveZoneSet(DWORD vkCode) noexcept; IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } IFACEMETHODIMP_(void) @@ -140,6 +138,8 @@ struct ZoneWindow : public winrt::implements UpdateActiveZoneSet() noexcept; IFACEMETHODIMP_(void) ClearSelectedZones() noexcept; + IFACEMETHODIMP_(void) + FlashZones() noexcept; protected: static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; @@ -149,9 +149,8 @@ struct ZoneWindow : public winrt::implements void CalculateZoneSet() noexcept; void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; - void OnKeyUp(WPARAM wparam) noexcept; std::vector ZonesFromPoint(POINT pt) noexcept; - void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept; + void SetAsTopmostWindow() noexcept; winrt::com_ptr m_host; HMONITOR m_monitor{}; @@ -165,7 +164,7 @@ struct ZoneWindow : public winrt::implements WPARAM m_keyLast{}; size_t m_keyCycle{}; static const UINT m_showAnimationDuration = 200; // ms - static const UINT m_flashDuration = 700; // ms + static const UINT m_flashDuration = 1000; // ms std::unique_ptr m_zoneWindowDrawing; }; @@ -359,17 +358,6 @@ ZoneWindow::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexce return false; } -IFACEMETHODIMP_(void) -ZoneWindow::CycleActiveZoneSet(DWORD wparam) noexcept -{ - CycleActiveZoneSetInternal(wparam, Trace::ZoneWindow::InputMode::Keyboard); - - if (m_windowMoveSize) - { - InvalidateRect(m_window, nullptr, true); - } -} - IFACEMETHODIMP_(void) ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept { @@ -392,23 +380,12 @@ ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept IFACEMETHODIMP_(void) ZoneWindow::ShowZoneWindow() noexcept { - auto window = m_window; - if (!window) - { - return; - } - - UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; - - HWND windowInsertAfter = m_windowMoveSize; - if (windowInsertAfter == nullptr) + if (m_window) { - windowInsertAfter = HWND_TOPMOST; + SetAsTopmostWindow(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + m_zoneWindowDrawing->Show(m_showAnimationDuration); } - - SetWindowPos(window, windowInsertAfter, 0, 0, 0, 0, flags); - m_zoneWindowDrawing->Show(m_showAnimationDuration); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); } IFACEMETHODIMP_(void) @@ -427,6 +404,10 @@ IFACEMETHODIMP_(void) ZoneWindow::UpdateActiveZoneSet() noexcept { CalculateZoneSet(); + if (m_window) + { + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + } } IFACEMETHODIMP_(void) @@ -439,6 +420,17 @@ ZoneWindow::ClearSelectedZones() noexcept } } +IFACEMETHODIMP_(void) +ZoneWindow::FlashZones() noexcept +{ + if (m_window) + { + SetAsTopmostWindow(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), {}, m_host); + m_zoneWindowDrawing->Flash(m_flashDuration); + } +} + #pragma region private void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept @@ -541,12 +533,6 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept case WM_ERASEBKGND: return 1; - case WM_PAINT: - { - m_zoneWindowDrawing->ForceRender(); - break; - } - default: { return DefWindowProc(m_window, message, wparam, lparam); @@ -555,18 +541,6 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept return 0; } -void ZoneWindow::OnKeyUp(WPARAM wparam) noexcept -{ - bool fRedraw = false; - Trace::ZoneWindow::KeyUp(wparam); - - if ((wparam >= '0') && (wparam <= '9')) - { - CycleActiveZoneSetInternal(static_cast(wparam), Trace::ZoneWindow::InputMode::Keyboard); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); - } -} - std::vector ZoneWindow::ZonesFromPoint(POINT pt) noexcept { if (m_activeZoneSet) @@ -576,52 +550,22 @@ std::vector ZoneWindow::ZonesFromPoint(POINT pt) noexcept return {}; } -void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept +void ZoneWindow::SetAsTopmostWindow() noexcept { - Trace::ZoneWindow::CycleActiveZoneSet(m_activeZoneSet, mode); - if (m_keyLast != wparam) + if (!m_window) { - m_keyCycle = 0; + return; } - m_keyLast = wparam; - - bool loopAround = true; - size_t const val = static_cast(wparam - L'0'); - size_t i = 0; - for (auto zoneSet : m_zoneSets) - { - if (zoneSet->GetZones().size() == val) - { - if (i < m_keyCycle) - { - i++; - } - else - { - UpdateActiveZoneSet(zoneSet.get()); - loopAround = false; - break; - } - } - } + UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; - if ((m_keyCycle > 0) && loopAround) - { - // Cycling through a non-empty group and hit the end - m_keyCycle = 0; - OnKeyUp(wparam); - } - else + HWND windowInsertAfter = m_windowMoveSize; + if (windowInsertAfter == nullptr) { - m_keyCycle++; + windowInsertAfter = HWND_TOPMOST; } - if (m_host) - { - m_host->MoveWindowsOnActiveZoneSetChange(); - } - m_highlightZone = {}; + SetWindowPos(m_window, windowInsertAfter, 0, 0, 0, 0, flags); } #pragma endregion diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 26dc63c9adec..708f4bd3c2bc 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -86,13 +86,6 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow * zones available in the given direction. */ IFACEMETHOD_(bool, ExtendWindowByDirectionAndPosition)(HWND window, DWORD vkCode) = 0; - /** - * Cycle through active zone layouts (giving hints about each layout). - * - * @param vkCode Pressed key representing layout index. - */ - IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0; - /** * Save information about zone in which window was assigned, when closing the window. * Used once we open same window again to assign it to its previous zone. @@ -118,6 +111,10 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow * Clear the selected zones when this ZoneWindow loses focus. */ IFACEMETHOD_(void, ClearSelectedZones)() = 0; + /* + * Display the layout on the screen and then hide it. + */ + IFACEMETHOD_(void, FlashZones)() = 0; }; winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp b/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp index e87b86d70c41..b47b62090fc0 100644 --- a/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp +++ b/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp @@ -19,18 +19,21 @@ float ZoneWindowDrawing::GetAnimationAlpha() // Lock is being held if (!m_animation) { - return 1.f; + return 0.f; } auto tNow = std::chrono::steady_clock().now(); auto alpha = (tNow - m_animation->tStart).count() / (1e6f * m_animation->duration); - if (alpha < 1.f) + + if (m_animation->fadeIn) { - return alpha; + // Return a positive value to avoid hiding + return std::clamp(alpha, 0.001f, 1.f); } else { - return 1.f; + // Quadratic function looks better + return std::clamp(1.f - alpha * alpha, 0.f, 1.f); } } @@ -100,28 +103,7 @@ ZoneWindowDrawing::ZoneWindowDrawing(HWND window) return; } - m_renderThread = std::thread([this]() { - while (!m_abortThread) - { - // Force repeated rendering while in the animation loop. - // Yield if low latency locking was requested - if (!m_lowLatencyLock) - { - float animationAlpha; - { - std::unique_lock lock(m_mutex); - animationAlpha = GetAnimationAlpha(); - } - - if (animationAlpha < 1.f) - { - m_shouldRender = true; - } - } - - Render(); - } - }); + m_renderThread = std::thread([this]() { RenderLoop(); }); } void ZoneWindowDrawing::Render() @@ -133,11 +115,14 @@ void ZoneWindowDrawing::Render() return; } - m_cv.wait(lock, [this]() { return (bool)m_shouldRender; }); + float animationAlpha = GetAnimationAlpha(); + + if (animationAlpha <= 0.f) + { + return; + } m_renderTarget->BeginDraw(); - - float animationAlpha = GetAnimationAlpha(); // Draw backdrop m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); @@ -197,20 +182,49 @@ void ZoneWindowDrawing::Render() textBrush->Release(); } + // The lock must be released here, as EndDraw() will wait for vertical sync + lock.unlock(); + m_renderTarget->EndDraw(); - m_shouldRender = false; +} + +void ZoneWindowDrawing::RenderLoop() +{ + while (!m_abortThread) + { + float animationAlpha; + { + // The lock must be held by the caller when calling GetAnimationAlpha + std::unique_lock lock(m_mutex); + animationAlpha = GetAnimationAlpha(); + } + + // Check whether the animation expired and we need to hide the window + if (animationAlpha <= 0.f) + { + Hide(); + } + + { + // Wait here while rendering is disabled + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this]() { return (bool)m_shouldRender; }); + } + + Render(); + } } void ZoneWindowDrawing::Hide() { _TRACER_; - m_lowLatencyLock = true; std::unique_lock lock(m_mutex); - m_lowLatencyLock = false; - if (m_animation) + m_animation.reset(); + + if (m_shouldRender) { - m_animation.reset(); + m_shouldRender = false; ShowWindow(m_window, SW_HIDE); } } @@ -218,20 +232,40 @@ void ZoneWindowDrawing::Hide() void ZoneWindowDrawing::Show(unsigned animationMillis) { _TRACER_; - m_lowLatencyLock = true; std::unique_lock lock(m_mutex); - m_lowLatencyLock = false; + + if (!m_shouldRender) + { + ShowWindow(m_window, SW_SHOWNA); + } + + animationMillis = max(animationMillis, 1); + + if (!m_animation || !m_animation->fadeIn) + { + m_animation.emplace(AnimationInfo{ std::chrono::steady_clock().now(), animationMillis, true }); + } + + m_shouldRender = true; + m_cv.notify_all(); +} + +void ZoneWindowDrawing::Flash(unsigned animationMillis) +{ + _TRACER_; + std::unique_lock lock(m_mutex); - if (!m_animation) + if (!m_shouldRender) { ShowWindow(m_window, SW_SHOWNA); - if (animationMillis > 0) - { - m_animation.emplace(AnimationInfo{ std::chrono::steady_clock().now(), animationMillis }); - } - m_shouldRender = true; - m_cv.notify_all(); } + + animationMillis = max(animationMillis, 1); + + m_animation.emplace(AnimationInfo{ std::chrono::steady_clock().now(), animationMillis, false }); + + m_shouldRender = true; + m_cv.notify_all(); } void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, @@ -239,9 +273,7 @@ void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, winrt::com_ptr host) { _TRACER_; - m_lowLatencyLock = true; std::unique_lock lock(m_mutex); - m_lowLatencyLock = false; m_sceneRects = {}; @@ -299,18 +331,6 @@ void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, m_sceneRects.push_back(drawableRect); } } - - m_shouldRender = true; - m_cv.notify_all(); -} - -void ZoneWindowDrawing::ForceRender() -{ - m_lowLatencyLock = true; - std::unique_lock lock(m_mutex); - m_lowLatencyLock = false; - m_shouldRender = true; - m_cv.notify_all(); } ZoneWindowDrawing::~ZoneWindowDrawing() diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.h b/src/modules/fancyzones/lib/ZoneWindowDrawing.h index 1485d866416b..b9cceb16b1d0 100644 --- a/src/modules/fancyzones/lib/ZoneWindowDrawing.h +++ b/src/modules/fancyzones/lib/ZoneWindowDrawing.h @@ -26,6 +26,7 @@ class ZoneWindowDrawing { std::chrono::steady_clock::time_point tStart; unsigned duration; + bool fadeIn; }; HWND m_window = nullptr; @@ -42,10 +43,10 @@ class ZoneWindowDrawing static D2D1_COLOR_F ConvertColor(COLORREF color); static D2D1_RECT_F ConvertRect(RECT rect); void Render(); + void RenderLoop(); std::atomic m_shouldRender = false; std::atomic m_abortThread = false; - std::atomic m_lowLatencyLock = false; std::condition_variable m_cv; std::thread m_renderThread; @@ -55,7 +56,7 @@ class ZoneWindowDrawing ZoneWindowDrawing(HWND window); void Hide(); void Show(unsigned animationMillis); - void ForceRender(); + void Flash(unsigned animationMillis); void DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, const std::vector& highlightZones, winrt::com_ptr host); diff --git a/src/modules/fancyzones/lib/trace.cpp b/src/modules/fancyzones/lib/trace.cpp index 9961c2f8f4db..e1265f58b1bf 100644 --- a/src/modules/fancyzones/lib/trace.cpp +++ b/src/modules/fancyzones/lib/trace.cpp @@ -17,6 +17,7 @@ #define EventZoneWindowKeyUpKey "FancyZones_ZoneWindowKeyUp" #define EventMoveSizeEndKey "FancyZones_MoveSizeEnd" #define EventCycleActiveZoneSetKey "FancyZones_CycleActiveZoneSet" +#define EventQuickLayoutSwitchKey "FancyZones_QuickLayoutSwitch" #define EventEnabledKey "Enabled" #define PressedKeyCodeKey "Hotkey" @@ -25,6 +26,7 @@ #define MoveSizeActionKey "InMoveSize" #define AppsInHistoryCountKey "AppsInHistoryCount" #define CustomZoneSetCountKey "CustomZoneSetCount" +#define LayoutUsingQuickKeyCountKey "LayoutUsingQuickKeyCount" #define NumberOfZonesForEachCustomZoneSetKey "NumberOfZonesForEachCustomZoneSet" #define ActiveZoneSetsCountKey "ActiveZoneSetsCount" #define ActiveZoneSetsListKey "ActiveZoneSetsList" @@ -40,6 +42,8 @@ #define MoveWindowsToLastZoneOnAppOpeningKey "MoveWindowsToLastZoneOnAppOpening" #define OpenWindowOnActiveMonitorKey "OpenWindowOnActiveMonitor" #define RestoreSizeKey "RestoreSize" +#define QuickLayoutSwitchKey "QuickLayoutSwitch" +#define FlashZonesOnQuickSwitchKey "FlashZonesOnQuickSwitch" #define UseCursorPosOnEditorStartupKey "UseCursorPosOnEditorStartup" #define ShowZonesOnAllMonitorsKey "ShowZonesOnAllMonitors" #define SpanZonesAcrossMonitorsKey "SpanZonesAcrossMonitors" @@ -56,6 +60,7 @@ #define NumberOfWindowsKey "NumberOfWindows" #define InputModeKey "InputMode" #define OverlappingZonesAlgorithmKey "OverlappingZonesAlgorithm" +#define QuickLayoutSwitchedWithShortcutUsed "ShortcutUsed" TRACELOGGING_DEFINE_PROVIDER( g_hProvider, @@ -128,6 +133,7 @@ void Trace::FancyZones::DataChanged() noexcept int appsHistorySize = static_cast(data.GetAppZoneHistoryMap().size()); const auto& customZones = data.GetCustomZoneSetsMap(); const auto& devices = data.GetDeviceInfoMap(); + const auto& quickKeys = data.GetLayoutQuickKeys(); std::unique_ptr customZonesArray(new (std::nothrow) INT32[customZones.size()]); if (!customZonesArray) @@ -149,7 +155,7 @@ void Trace::FancyZones::DataChanged() noexcept return 0; }; - //NumberOfZonesForEachCustomZoneSet + // NumberOfZonesForEachCustomZoneSet int i = 0; for (const auto& [id, customZoneSetData] : customZones) { @@ -157,7 +163,7 @@ void Trace::FancyZones::DataChanged() noexcept i++; } - //ActiveZoneSetsList + // ActiveZoneSetsList std::wstring activeZoneSetInfo; for (const auto& [id, device] : devices) { @@ -201,7 +207,8 @@ void Trace::FancyZones::DataChanged() noexcept TraceLoggingInt32(static_cast(customZones.size()), CustomZoneSetCountKey), TraceLoggingInt32Array(customZonesArray.get(), static_cast(customZones.size()), NumberOfZonesForEachCustomZoneSetKey), TraceLoggingInt32(static_cast(devices.size()), ActiveZoneSetsCountKey), - TraceLoggingWideString(activeZoneSetInfo.c_str(), ActiveZoneSetsListKey)); + TraceLoggingWideString(activeZoneSetInfo.c_str(), ActiveZoneSetsListKey), + TraceLoggingInt32(static_cast(quickKeys.size()), LayoutUsingQuickKeyCountKey)); } void Trace::FancyZones::EditorLaunched(int value) noexcept @@ -227,6 +234,16 @@ void Trace::FancyZones::Error(const DWORD errorCode, std::wstring errorMessage, TraceLoggingValue(errorMessage.c_str(), "ErrorMessage")); } +void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept +{ + TraceLoggingWrite( + g_hProvider, + EventQuickLayoutSwitchKey, + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); +} + void Trace::SettingsChanged(const Settings& settings) noexcept { const auto& editorHotkey = settings.editorHotkey; @@ -253,6 +270,8 @@ void Trace::SettingsChanged(const Settings& settings) noexcept TraceLoggingBoolean(settings.appLastZone_moveWindows, MoveWindowsToLastZoneOnAppOpeningKey), TraceLoggingBoolean(settings.openWindowOnActiveMonitor, OpenWindowOnActiveMonitorKey), TraceLoggingBoolean(settings.restoreSize, RestoreSizeKey), + TraceLoggingBoolean(settings.quickLayoutSwitch, QuickLayoutSwitchKey), + TraceLoggingBoolean(settings.flashZonesOnQuickSwitch, FlashZonesOnQuickSwitchKey), TraceLoggingBoolean(settings.use_cursorpos_editor_startupscreen, UseCursorPosOnEditorStartupKey), TraceLoggingBoolean(settings.showZonesOnAllMonitors, ShowZonesOnAllMonitorsKey), TraceLoggingBoolean(settings.spanZonesAcrossMonitors, SpanZonesAcrossMonitorsKey), diff --git a/src/modules/fancyzones/lib/trace.h b/src/modules/fancyzones/lib/trace.h index d33c4734cf35..59d42f0f255b 100644 --- a/src/modules/fancyzones/lib/trace.h +++ b/src/modules/fancyzones/lib/trace.h @@ -17,6 +17,7 @@ class Trace static void DataChanged() noexcept; static void EditorLaunched(int value) noexcept; static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; + static void QuickLayoutSwitched(bool shortcutUsed) noexcept; }; static void SettingsChanged(const Settings& settings) noexcept; diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp index d52eabb24581..b8077c768b84 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp @@ -78,6 +78,8 @@ namespace FancyZonesUnitTests ptSettings.add_bool_toggle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows); ptSettings.add_bool_toggle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows); ptSettings.add_bool_toggle(L"fancyzones_restoreSize", IDS_SETTING_DESCRIPTION_RESTORESIZE, settings.restoreSize); + ptSettings.add_bool_toggle(L"fancyzones_quickLayoutSwitch", IDS_SETTING_DESCRIPTION_QUICKLAYOUTSWITCH, settings.quickLayoutSwitch); + ptSettings.add_bool_toggle(L"fancyzones_flashZonesOnQuickSwitch", IDS_SETTING_DESCRIPTION_FLASHZONESONQUICKSWITCH, settings.flashZonesOnQuickSwitch); ptSettings.add_bool_toggle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen); ptSettings.add_bool_toggle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors); ptSettings.add_bool_toggle(L"fancyzones_multi_monitor_mode", IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS, settings.spanZonesAcrossMonitors); @@ -303,6 +305,8 @@ namespace FancyZonesUnitTests ptSettings.add_bool_toggle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows); ptSettings.add_bool_toggle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows); ptSettings.add_bool_toggle(L"fancyzones_restoreSize", IDS_SETTING_DESCRIPTION_RESTORESIZE, settings.restoreSize); + ptSettings.add_bool_toggle(L"fancyzones_quickLayoutSwitch", IDS_SETTING_DESCRIPTION_QUICKLAYOUTSWITCH, settings.quickLayoutSwitch); + ptSettings.add_bool_toggle(L"fancyzones_flashZonesOnQuickSwitch", IDS_SETTING_DESCRIPTION_FLASHZONESONQUICKSWITCH, settings.flashZonesOnQuickSwitch); ptSettings.add_bool_toggle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen); ptSettings.add_bool_toggle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors); ptSettings.add_bool_toggle(L"fancyzones_multi_monitor_mode", IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS, settings.spanZonesAcrossMonitors); diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 094681c69556..76aaadeb7ffa 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -1539,6 +1539,73 @@ namespace FancyZonesUnitTests compareJsonArrays(expected, actual); } + TEST_METHOD(QuickLayoutKeysParse) + { + const std::wstring zoneUuid = L"{33A2B101-06E0-437B-A61E-CDBECF502906}"; + LayoutQuickKeyJSON expected{ zoneUuid, 2 }; + json::JsonArray array; + array.Append(LayoutQuickKeyJSON::ToJson(expected)); + + json::JsonObject json; + json.SetNamedValue(L"quick-layout-keys", json::JsonValue::Parse(array.Stringify())); + + const auto& quickKeysMap = ParseQuickKeys(json); + + Assert::AreEqual((size_t)array.Size(), quickKeysMap.size()); + + Assert::IsTrue(quickKeysMap.find(zoneUuid) != quickKeysMap.end()); + int actualKey = quickKeysMap.find(zoneUuid)->second; + Assert::AreEqual((int)expected.key, actualKey); + } + + TEST_METHOD (QuickLayoutKeysParseEmpty) + { + json::JsonArray array; + json::JsonObject json; + json.SetNamedValue(L"quick-layout-keys", json::JsonValue::Parse(array.Stringify())); + + const auto& quickKeysMap = ParseQuickKeys(json); + + Assert::IsTrue(quickKeysMap.empty()); + } + + TEST_METHOD (QuickLayoutKeysParseInvalid) + { + const std::wstring invalidZoneUuid = L"{33A2B101-06E0-437B-}"; + LayoutQuickKeyJSON expected{ invalidZoneUuid, 2 }; + json::JsonArray array; + array.Append(LayoutQuickKeyJSON::ToJson(expected)); + + json::JsonObject json; + json.SetNamedValue(L"quick-layout-keys", json::JsonValue::Parse(array.Stringify())); + + const auto& quickKeysMap = ParseQuickKeys(json); + + Assert::IsTrue(quickKeysMap.empty()); + } + + TEST_METHOD (QuickLayoutKeysParseMissed) + { + json::JsonObject json; + + const auto& quickKeysMap = ParseQuickKeys(json); + + Assert::IsTrue(quickKeysMap.empty()); + } + + TEST_METHOD (QuickLayoutKeysSerialize) + { + json::JsonArray expected; + expected.Append(LayoutQuickKeyJSON::ToJson(LayoutQuickKeyJSON{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", 3})); + json::JsonObject json; + json.SetNamedValue(L"quick-layout-keys", json::JsonValue::Parse(expected.Stringify())); + + const auto& quickKeysMap = ParseQuickKeys(json); + + auto actual = SerializeQuickKeys(quickKeysMap); + compareJsonArrays(expected, actual); + } + TEST_METHOD (SetActiveZoneSet) { FancyZonesData data; @@ -1640,14 +1707,17 @@ namespace FancyZonesUnitTests }; AppZoneHistoryJSON appZoneHistory{ L"app-path", std::vector{ data } }; DeviceInfoJSON deviceInfo{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; - json::JsonArray zoneSetsArray, appZonesArray, deviceInfoArray; + LayoutQuickKeyJSON quickKeys{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", 1 }; + json::JsonArray zoneSetsArray, appZonesArray, deviceInfoArray, quickKeysArray; zoneSetsArray.Append(CustomZoneSetJSON::ToJson(zoneSets)); appZonesArray.Append(AppZoneHistoryJSON::ToJson(appZoneHistory)); deviceInfoArray.Append(DeviceInfoJSON::ToJson(deviceInfo)); + quickKeysArray.Append(LayoutQuickKeyJSON::ToJson(quickKeys)); json::JsonObject fancyZones; fancyZones.SetNamedValue(L"custom-zone-sets", zoneSetsArray); fancyZones.SetNamedValue(L"app-zone-history", appZonesArray); fancyZones.SetNamedValue(L"devices", deviceInfoArray); + fancyZones.SetNamedValue(L"quick-layout-keys", quickKeysArray); json::to_file(jsonPath, fancyZones); @@ -1664,6 +1734,7 @@ namespace FancyZonesUnitTests Assert::IsFalse(fancyZonesData.GetCustomZoneSetsMap().empty()); Assert::IsFalse(fancyZonesData.GetCustomZoneSetsMap().empty()); Assert::IsFalse(fancyZonesData.GetCustomZoneSetsMap().empty()); + Assert::IsFalse(fancyZonesData.GetLayoutQuickKeys().empty()); } TEST_METHOD (LoadFancyZonesDataFromCroppedJson) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ConfigDefaults.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ConfigDefaults.cs index 12a481906378..aa8299b3a72c 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ConfigDefaults.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ConfigDefaults.cs @@ -14,5 +14,7 @@ public static class ConfigDefaults // Fancy Zones Default Flags. public static readonly bool DefaultFancyzonesShiftDrag = true; public static readonly bool DefaultUseCursorposEditorStartupscreen = true; + public static readonly bool DefaultFancyzonesQuickLayoutSwitch = true; + public static readonly bool DefaultFancyzonesFlashZonesOnQuickSwitch = true; } } 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 f515f2e852a1..56ce046585ca 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs @@ -24,6 +24,8 @@ public FZConfigProperties() FancyzonesAppLastZoneMoveWindows = new BoolProperty(); FancyzonesOpenWindowOnActiveMonitor = new BoolProperty(); FancyzonesRestoreSize = new BoolProperty(); + FancyzonesQuickLayoutSwitch = new BoolProperty(ConfigDefaults.DefaultFancyzonesQuickLayoutSwitch); + FancyzonesFlashZonesOnQuickSwitch = new BoolProperty(ConfigDefaults.DefaultFancyzonesFlashZonesOnQuickSwitch); UseCursorposEditorStartupscreen = new BoolProperty(ConfigDefaults.DefaultUseCursorposEditorStartupscreen); FancyzonesShowOnAllMonitors = new BoolProperty(); FancyzonesSpanZonesAcrossMonitors = new BoolProperty(); @@ -69,6 +71,12 @@ public FZConfigProperties() [JsonPropertyName("fancyzones_restoreSize")] public BoolProperty FancyzonesRestoreSize { get; set; } + [JsonPropertyName("fancyzones_quickLayoutSwitch")] + public BoolProperty FancyzonesQuickLayoutSwitch { get; set; } + + [JsonPropertyName("fancyzones_flashZonesOnQuickSwitch")] + public BoolProperty FancyzonesFlashZonesOnQuickSwitch { get; set; } + [JsonPropertyName("use_cursorpos_editor_startupscreen")] public BoolProperty UseCursorposEditorStartupscreen { 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 d21db334d0aa..82d6497e5dde 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 @@ -71,6 +71,8 @@ public FancyZonesViewModel(ISettingsRepository settingsReposito _appLastZoneMoveWindows = Settings.Properties.FancyzonesAppLastZoneMoveWindows.Value; _openWindowOnActiveMonitor = Settings.Properties.FancyzonesOpenWindowOnActiveMonitor.Value; _restoreSize = Settings.Properties.FancyzonesRestoreSize.Value; + _quickLayoutSwitch = Settings.Properties.FancyzonesQuickLayoutSwitch.Value; + _flashZonesOnQuickLayoutSwitch = Settings.Properties.FancyzonesFlashZonesOnQuickSwitch.Value; _useCursorPosEditorStartupScreen = Settings.Properties.UseCursorposEditorStartupscreen.Value; _showOnAllMonitors = Settings.Properties.FancyzonesShowOnAllMonitors.Value; _spanZonesAcrossMonitors = Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value; @@ -107,6 +109,8 @@ public FancyZonesViewModel(ISettingsRepository settingsReposito private bool _openWindowOnActiveMonitor; private bool _spanZonesAcrossMonitors; private bool _restoreSize; + private bool _quickLayoutSwitch; + private bool _flashZonesOnQuickLayoutSwitch; private bool _useCursorPosEditorStartupScreen; private bool _showOnAllMonitors; private bool _makeDraggedWindowTransparent; @@ -138,6 +142,7 @@ public bool IsEnabled SendConfigMSG(snd.ToString()); OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(SnapHotkeysCategoryEnabled)); + OnPropertyChanged(nameof(QuickSwitchEnabled)); } } } @@ -150,6 +155,14 @@ public bool SnapHotkeysCategoryEnabled } } + public bool QuickSwitchEnabled + { + get + { + return _isEnabled && _quickLayoutSwitch; + } + } + public bool ShiftDrag { get @@ -374,6 +387,43 @@ public bool RestoreSize } } + public bool QuickLayoutSwitch + { + get + { + return _quickLayoutSwitch; + } + + set + { + if (value != _quickLayoutSwitch) + { + _quickLayoutSwitch = value; + Settings.Properties.FancyzonesQuickLayoutSwitch.Value = value; + NotifyPropertyChanged(); + OnPropertyChanged(nameof(QuickSwitchEnabled)); + } + } + } + + public bool FlashZonesOnQuickSwitch + { + get + { + return _flashZonesOnQuickLayoutSwitch; + } + + set + { + if (value != _flashZonesOnQuickLayoutSwitch) + { + _flashZonesOnQuickLayoutSwitch = value; + Settings.Properties.FancyzonesFlashZonesOnQuickSwitch.Value = value; + NotifyPropertyChanged(); + } + } + } + public bool UseCursorPosEditorStartupScreen { get 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 9cc66b5d461d..e8a429e8f0c5 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 @@ -1176,4 +1176,13 @@ Win + Shift + O to toggle your video Move the color up + + Flash zones when switching layout + + + Enable quick layout switch + + + Quick layout switch + \ No newline at end of file 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 f20aa59cde27..a72cec950835 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -189,6 +189,20 @@ IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"/> + + + + + +