From ac94bbe57527e7daf9f21415775dd19fb1867586 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 13 Oct 2020 22:24:15 +0200 Subject: [PATCH 01/22] Added improved narrator support for custom shortcut control --- .../Strings/en-us/Resources.resw | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 380e19b3c3..e4ac27c91a 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -337,7 +337,7 @@ Open zones editor - Set Shortcut + Enter shortcut Information Symbol @@ -768,4 +768,13 @@ A reboot may be required for changes to these settings to take effect - + + Open Color Picker + + + Open zones editor + + + Open PowerToys Run + + \ No newline at end of file From 3f4336697e5aa11a9f1ddb761f7082819ac23c55 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 13 Oct 2020 23:30:29 +0200 Subject: [PATCH 02/22] Improvements --- .../Controls/HotkeySettingsControl.xaml | 12 +++++++----- .../Strings/en-us/Resources.resw | 11 +---------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml index 7611e743e8..8b49e30aa2 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml @@ -5,16 +5,18 @@ xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + AutomationProperties.Name="{x:Bind Header, Mode=OneTime}" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> - - - - + + + + + @@ -24,7 +26,7 @@ Foreground="{Binding Path=IsEnabled, ElementName=HotkeyTextBox, Converter={StaticResource ModuleEnabledToForegroundConverter}}" /> - Open zones editor - Enter shortcut + Set shortcut Information Symbol @@ -768,13 +768,4 @@ A reboot may be required for changes to these settings to take effect - - Open Color Picker - - - Open zones editor - - - Open PowerToys Run - \ No newline at end of file From 466a0015e952fc32644e14173bbc3f770c117049 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 23 Oct 2020 15:44:31 +0200 Subject: [PATCH 03/22] Fixed tooltip call out with narrator --- .../Controls/HotkeySettingsControl.xaml | 47 +++++++------------ .../Strings/en-us/Resources.resw | 16 +++---- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml index 8b49e30aa2..89e2313a95 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml @@ -5,39 +5,26 @@ xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - AutomationProperties.Name="{x:Bind Header, Mode=OneTime}" mc:Ignorable="d" + AutomationProperties.Name="{x:Bind Header, Mode=OneTime}" d:DesignHeight="300" d:DesignWidth="400"> - + - - - - - - - + + + + + - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index a587a376d4..6c4d136aa5 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -204,10 +204,10 @@ Current Shortcut Remappings - Key Remapping + Key remapping - Shortcut Remapping + Shortcut remapping Remapped to @@ -337,7 +337,7 @@ Open zones editor - Set shortcut + Enter your shortcut Information Symbol @@ -502,7 +502,7 @@ Enable Image Resizer - Image Size + Image size Configurations @@ -511,19 +511,19 @@ Configuration Name - Fit Property + Fit property - Width Property + Width property Height Property - Size Property + Size property - Times Symbol + Times symbol Remove From 7c4216261889c06164cee5b62bf416c9cbe01939 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 23 Oct 2020 15:57:34 +0200 Subject: [PATCH 04/22] Updated string --- .../Strings/en-us/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 6c4d136aa5..9e9762952c 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -337,7 +337,7 @@ Open zones editor - Enter your shortcut + Shortcut setting Information Symbol From 773c57e6094ef995875faeccb569fe09ad304218 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 23 Oct 2020 16:10:16 +0200 Subject: [PATCH 05/22] File format limitations are now announced by Narrator --- .../Views/ImageResizerPage.xaml | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml index 3939cea8ec..d7c98c4f64 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml @@ -255,7 +255,9 @@ MinWidth="240" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}" Margin="{StaticResource SmallTopMargin}" - AutomationProperties.LabeledBy="{Binding ElementName=ImageResizer_FilenameFormatHeader}"> + AutomationProperties.LabeledBy="{Binding ElementName=ImageResizer_FilenameFormatHeader}" + AutomationProperties.HelpText="{Binding ElementName=FileFormatTextBlock, Path=Text}" + > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 36b628df48de4c900265e537203e310bfe77203c Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Sat, 24 Oct 2020 15:06:12 +0200 Subject: [PATCH 06/22] Fixed fancy zones MDL2 icon --- .../Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml | 2 +- src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index 5d0edf9616..2add0e20bf 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -63,7 +63,7 @@ AutomationProperties.LabeledBy="{Binding ElementName=FancyZones_LaunchEditorButtonControl}"> - + - + From 61a2e5297fa006a87036178132f668d50157d8b7 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Sat, 24 Oct 2020 16:35:10 +0200 Subject: [PATCH 07/22] Fix resource issue --- .../Strings/en-us/Resources.resw | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index e48dbc0734..93002886a0 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -340,7 +340,7 @@ Open zones editor - Set Shortcut + Shortcut setting Information Symbol @@ -786,7 +786,4 @@ Light - - Set Shortcut Shortcut setting - \ No newline at end of file From bea1cf92d8bdead1e915c418c2456691a95588b0 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Sun, 25 Oct 2020 15:21:14 +0100 Subject: [PATCH 08/22] Updated FZ icon --- doc/images/icons/FancyZones_MDL2.svg | 5 +++++ .../Views/FancyZonesPage.xaml | 6 +++--- .../Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml | 9 +-------- 3 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 doc/images/icons/FancyZones_MDL2.svg diff --git a/doc/images/icons/FancyZones_MDL2.svg b/doc/images/icons/FancyZones_MDL2.svg new file mode 100644 index 0000000000..5173874aa1 --- /dev/null +++ b/doc/images/icons/FancyZones_MDL2.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index 2add0e20bf..ea44240466 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -62,10 +62,10 @@ IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}" AutomationProperties.LabeledBy="{Binding ElementName=FancyZones_LaunchEditorButtonControl}"> - - + + - diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml index a9bc4f6842..a5b950c92f 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml @@ -41,10 +41,9 @@ - - + @@ -54,35 +53,30 @@ - - - - - @@ -94,7 +88,6 @@ DefaultHeader="{x:Bind ViewModel.Selected.Content, Mode=OneWay}"> - Date: Sun, 25 Oct 2020 18:00:59 +0100 Subject: [PATCH 09/22] Update string color represention when color format changed, but mouse is hovering on top of the same color --- .../ColorPickerUI/Mouse/MouseInfoProvider.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs index 5dd74771d0..2389c7c268 100644 --- a/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs +++ b/src/modules/colorPicker/ColorPickerUI/Mouse/MouseInfoProvider.cs @@ -24,6 +24,7 @@ namespace ColorPicker.Mouse private readonly IUserSettings _userSettings; private System.Windows.Point _previousMousePosition = new System.Windows.Point(-1, 1); private Color _previousColor = Color.Transparent; + private bool _colorFormatChanged; [ImportingConstructor] public MouseInfoProvider(AppStateHandler appStateMonitor, IUserSettings userSettings) @@ -40,6 +41,7 @@ namespace ColorPicker.Mouse _mouseHook = new MouseHook(); _userSettings = userSettings; + _userSettings.CopiedColorRepresentation.PropertyChanged += CopiedColorRepresentation_PropertyChanged; } public event EventHandler MouseColorChanged; @@ -73,9 +75,10 @@ namespace ColorPicker.Mouse } var color = GetPixelColor(mousePosition); - if (_previousColor != color) + if (_previousColor != color || _colorFormatChanged) { _previousColor = color; + _colorFormatChanged = false; MouseColorChanged?.Invoke(this, color); } } @@ -137,6 +140,11 @@ namespace ColorPicker.Mouse OnMouseDown?.Invoke(this, p); } + private void CopiedColorRepresentation_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + _colorFormatChanged = true; + } + private void DisposeHook() { if (_timer.IsEnabled) From 8ea09e0c95b7539ff03465413ed5aad7305b6628 Mon Sep 17 00:00:00 2001 From: Enrico Giordani Date: Sun, 25 Oct 2020 22:54:48 +0100 Subject: [PATCH 10/22] [Keyboard Manager] new icon (#7492) --- src/modules/keyboardmanager/dll/Keyboard.ico | Bin 9662 -> 112652 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/modules/keyboardmanager/dll/Keyboard.ico b/src/modules/keyboardmanager/dll/Keyboard.ico index cc9c2753ee1449b2c3bd8f04d2e10c75c9d6da5c..16f379d24d26b321a1daf503be4b8001cd4c3613 100644 GIT binary patch literal 112652 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 Sf1gIaPg}Brozi0NXZv49i)<+X literal 9662 zcmeHKu}T9`5ZoZfMocF*LJCF8v$yjP1O<^`A{P1=X|%Ud(P&O=)E}@F(&Q`r1xq{Q z>>V#ImmADsH1FW8%}4q#&oC%)^p`aYm{*=*q4 z={Gs7}E16ElZ(;*^vk2dihQWIB zuFG=ctYdqaPR2h&rMs_eJ|3zUc_KZwpVi~6b7DS=>12G__3Pg|2J9@-bG>I=Xipcg zJ>%3duK8&lUDx(7pBazKy$Yt2@m`6C7^ai)Y_`~$V0$LT+c{;|WI7pN^!a7;q$qFa z)ql>PqB>dsr1Te=KQZo4J1g8=D$3j073L32^(feL%J{#SEq0z1U7zV>yq!OG4or(@ zIvGDLo$Pa&PR6_Wlbr+8;+amyd-UigQkc#dzjgmJc6}3V^jsR9_WNVU@DHf<{_qif zqqBk9#Q_TEe_8UH6OUX4UP`owQ={FLvncd^>Kf4J#J-34P-K43fYL(1i1=I#UyAYo5~sVkS+RYx;S;?>t|XN*m`akM=LM(*FEB(jWEaBVC{5 SY=2g7a*lnpwZOh6<<}RLRsksh From 8ef91a528fb1dcdc2ee3d989eb9be8b731b59423 Mon Sep 17 00:00:00 2001 From: Aaron Junker Date: Sun, 25 Oct 2020 23:39:04 +0100 Subject: [PATCH 11/22] Fixed #7524 (#7525) --- doc/devdocs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devdocs/settings.md b/doc/devdocs/settings.md index 583c42aca4..b5b98af611 100644 --- a/doc/devdocs/settings.md +++ b/doc/devdocs/settings.md @@ -284,4 +284,4 @@ Contains the main executable code, initializing and managing the Window containi Defines a class implementing `IUriToStreamResolver`. Allows the WebView to navigate to filesystem files in this Win32 project. ### [settings-html/](/src/settings/settings-html/) -Contains the assets file from building the [Web project for the Settings UI](/src/settings./settings-web). It will be loaded by the WebView. +Contains the assets file from building the [Web project for the Settings UI](/src/settings/settings-html). It will be loaded by the WebView. From e6a5b589ffef37335a75483d8093351ac853534f Mon Sep 17 00:00:00 2001 From: stefansjfw <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 26 Oct 2020 09:07:11 +0100 Subject: [PATCH 12/22] [FancyZones] Align zone numbers between Editor and FancyZonesLib (#7387) * Align zone numbers between Editor and FancyZonesLib Use zoneId on win+arrow * Update tests * Fix crash * ZoneId starts from 0 * Define ZonesMap type IFACEMETHOD_ macro is having trouble processing std::map template when expanding, so return type needs to be predefined * Address PR comment * Address more PR comments * Handle .at --- .../FancyZonesEditor/Models/Settings.cs | 12 +- src/modules/fancyzones/lib/FancyZones.cpp | 14 +- src/modules/fancyzones/lib/Zone.cpp | 2 +- src/modules/fancyzones/lib/ZoneSet.cpp | 135 +++++++++++------- src/modules/fancyzones/lib/ZoneSet.h | 6 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 4 +- .../fancyzones/lib/ZoneWindowDrawing.cpp | 42 +++--- .../fancyzones/lib/ZoneWindowDrawing.h | 4 +- .../tests/UnitTests/ZoneSet.Spec.cpp | 65 ++++----- 9 files changed, 150 insertions(+), 134 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 87a0af5015..c335ae6df1 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -404,15 +404,15 @@ namespace FancyZonesEditor _gridModel.ColumnPercents.Add(((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols)); } - int index = ZoneCount - 1; - for (int col = cols - 1; col >= 0; col--) + int index = 0; + for (int row = 0; row < rows; row++) { - for (int row = rows - 1; row >= 0; row--) + for (int col = 0; col < cols; col++) { - _gridModel.CellChildMap[row, col] = index--; - if (index < 0) + _gridModel.CellChildMap[row, col] = index++; + if (index == ZoneCount) { - index = 0; + index--; } } } diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 6699e2810d..44d02f0d58 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -1183,10 +1183,9 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto zoneSet = workArea->ActiveZoneSet(); if (zoneSet) { - auto zones = zoneSet->GetZones(); - for (size_t i = 0; i < zones.size(); i++) + const auto zones = zoneSet->GetZones(); + for (const auto& [zoneId, zone] : zones) { - const auto& zone = zones[i]; RECT zoneRect = zone->GetZoneRect(); zoneRect.left += monitorRect.left; @@ -1195,7 +1194,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept zoneRect.bottom += monitorRect.top; zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(i, workArea); + zoneRectsInfo.emplace_back(zoneId, workArea); } } } @@ -1228,10 +1227,9 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto zoneSet = workArea->ActiveZoneSet(); if (zoneSet) { - auto zones = zoneSet->GetZones(); - for (size_t i = 0; i < zones.size(); i++) + const auto zones = zoneSet->GetZones(); + for (const auto& [zoneId, zone] : zones) { - const auto& zone = zones[i]; RECT zoneRect = zone->GetZoneRect(); zoneRect.left += currentMonitorRect.left; @@ -1240,7 +1238,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept zoneRect.bottom += currentMonitorRect.top; zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(i, workArea); + zoneRectsInfo.emplace_back(zoneId, workArea); } } } diff --git a/src/modules/fancyzones/lib/Zone.cpp b/src/modules/fancyzones/lib/Zone.cpp index 0e1e428d30..b6493933e2 100644 --- a/src/modules/fancyzones/lib/Zone.cpp +++ b/src/modules/fancyzones/lib/Zone.cpp @@ -135,7 +135,7 @@ RECT Zone::ComputeActualZoneRect(HWND window, HWND zoneWindow) const noexcept winrt::com_ptr MakeZone(const RECT& zoneRect, const size_t zoneId) noexcept { - if (ValidateZoneRect(zoneRect) && zoneId > 0) + if (ValidateZoneRect(zoneRect) && zoneId >= 0) { return winrt::make_self(zoneRect, zoneId); } diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index 05ce569518..42a697b4e6 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -10,6 +10,8 @@ #include +#include +#include #include using namespace FancyZonesUtils; @@ -113,7 +115,7 @@ public: { } - ZoneSet(ZoneSetConfig const& config, std::vector> zones) : + ZoneSet(ZoneSetConfig const& config, ZonesMap zones) : m_config(config), m_zones(zones) { @@ -128,8 +130,8 @@ public: ZonesFromPoint(POINT pt) const noexcept; IFACEMETHODIMP_(std::vector) GetZoneIndexSetFromWindow(HWND window) const noexcept; - IFACEMETHODIMP_(std::vector>) - GetZones() const noexcept { return m_zones; } + IFACEMETHODIMP_(ZonesMap) + GetZones()const noexcept override { return m_zones; } IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, size_t index) noexcept; IFACEMETHODIMP_(void) @@ -157,7 +159,7 @@ private: bool CalculateCustomLayout(Rect workArea, int spacing) noexcept; bool CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutInfo gridLayoutInfo, int spacing); - std::vector> m_zones; + ZonesMap m_zones; std::map> m_windowIndexSet; // Needed for ExtendWindowByDirectionAndPosition @@ -170,7 +172,12 @@ private: IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr zone) noexcept { - m_zones.emplace_back(zone); + auto zoneId = zone->Id(); + if (m_zones.contains(zoneId)) + { + return S_FALSE; + } + m_zones[zoneId] = zone; return S_OK; } @@ -180,19 +187,19 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept { std::vector capturedZones; std::vector strictlyCapturedZones; - for (size_t i = 0; i < m_zones.size(); i++) + for (const auto& [zoneId, zone] : m_zones) { - const RECT& zoneRect = m_zones[i]->GetZoneRect(); + const RECT& zoneRect = zone->GetZoneRect(); if (zoneRect.left - m_config.SensitivityRadius <= pt.x && pt.x <= zoneRect.right + m_config.SensitivityRadius && zoneRect.top - m_config.SensitivityRadius <= pt.y && pt.y <= zoneRect.bottom + m_config.SensitivityRadius) { - capturedZones.emplace_back(i); + capturedZones.emplace_back(zoneId); } if (zoneRect.left <= pt.x && pt.x < zoneRect.right && zoneRect.top <= pt.y && pt.y < zoneRect.bottom) { - strictlyCapturedZones.emplace_back(i); + strictlyCapturedZones.emplace_back(zoneId); } } @@ -210,8 +217,18 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept { for (size_t j = i + 1; j < capturedZones.size(); ++j) { - const auto& rectI = m_zones[capturedZones[i]]->GetZoneRect(); - const auto& rectJ = m_zones[capturedZones[j]]->GetZoneRect(); + RECT rectI; + RECT rectJ; + try + { + rectI = m_zones.at(capturedZones[i])->GetZoneRect(); + rectJ = m_zones.at(capturedZones[j])->GetZoneRect(); + } + catch (std::out_of_range) + { + return {}; + } + if (max(rectI.top, rectJ.top) + m_config.SensitivityRadius < min(rectI.bottom, rectJ.bottom) && max(rectI.left, rectJ.left) + m_config.SensitivityRadius < min(rectI.right, rectJ.right)) { @@ -230,8 +247,17 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept size_t smallestIdx = 0; for (size_t i = 1; i < capturedZones.size(); ++i) { - const auto& rectS = m_zones[capturedZones[smallestIdx]]->GetZoneRect(); - const auto& rectI = m_zones[capturedZones[i]]->GetZoneRect(); + RECT rectS; + RECT rectI; + try + { + rectS = m_zones.at(capturedZones[smallestIdx])->GetZoneRect(); + rectI = m_zones.at(capturedZones[i])->GetZoneRect(); + } + catch (std::out_of_range) + { + return {}; + } int smallestSize = (rectS.bottom - rectS.top) * (rectS.right - rectS.left); int iSize = (rectI.bottom - rectI.top) * (rectI.right - rectI.left); @@ -267,7 +293,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, size_t inde } IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const std::vector& indexSet) noexcept +ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const std::vector& zoneIds) noexcept { if (m_zones.empty()) { @@ -287,11 +313,12 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const st m_windowIndexSet[window] = {}; - for (size_t index : indexSet) + for (size_t id : zoneIds) { - if (index < m_zones.size()) + if (m_zones.contains(id)) { - RECT newSize = m_zones.at(index)->ComputeActualZoneRect(window, workAreaWindow); + const auto& zone = m_zones.at(id); + const RECT newSize = zone->ComputeActualZoneRect(window, workAreaWindow); if (!sizeEmpty) { size.left = min(size.left, newSize.left); @@ -305,12 +332,12 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const st sizeEmpty = false; } - m_windowIndexSet[window].push_back(index); + m_windowIndexSet[window].push_back(id); } - if (index < std::numeric_limits::digits) + if (id < std::numeric_limits::digits) { - bitmask |= 1ull << index; + bitmask |= 1ull << id; } } @@ -336,14 +363,14 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, // The window was not assigned to any zone here if (indexSet.size() == 0) { - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { vkCode == VK_LEFT ? numZones - 1 : 0 }); + MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0); return true; } - size_t oldIndex = indexSet[0]; + size_t oldId = indexSet[0]; // We reached the edge - if ((vkCode == VK_LEFT && oldIndex == 0) || (vkCode == VK_RIGHT && oldIndex == numZones - 1)) + if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == numZones - 1)) { if (!cycle) { @@ -352,7 +379,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, } else { - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { vkCode == VK_LEFT ? numZones - 1 : 0 }); + MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0); return true; } } @@ -360,11 +387,11 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, // We didn't reach the edge if (vkCode == VK_LEFT) { - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { oldIndex - 1 }); + MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId - 1); } else { - MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { oldIndex + 1 }); + MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId + 1); } return true; } @@ -378,20 +405,20 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind } std::vector usedZoneIndices(m_zones.size(), false); - for (size_t idx : GetZoneIndexSetFromWindow(window)) + for (size_t id : GetZoneIndexSetFromWindow(window)) { - usedZoneIndices[idx] = true; + usedZoneIndices[id] = true; } std::vector zoneRects; std::vector freeZoneIndices; - for (size_t i = 0; i < m_zones.size(); i++) + for (const auto& [zoneId, zone] : m_zones) { - if (!usedZoneIndices[i]) + if (!usedZoneIndices[zoneId]) { - zoneRects.emplace_back(m_zones[i]->GetZoneRect()); - freeZoneIndices.emplace_back(i); + zoneRects.emplace_back(m_zones[zoneId]->GetZoneRect()); + freeZoneIndices.emplace_back(zoneId); } } @@ -415,7 +442,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind // Try again from the position off the screen in the opposite direction to vkCode // Consider all zones as available zoneRects.resize(m_zones.size()); - std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone->GetZoneRect(); }); + std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone.second->GetZoneRect(); }); windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, windowZoneRect, vkCode); result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); @@ -588,7 +615,7 @@ bool ZoneSet::CalculateFocusLayout(Rect workArea, int zoneCount) noexcept for (int i = 0; i < zoneCount; i++) { - auto zone = MakeZone(focusZoneRect, m_zones.size() + 1); + auto zone = MakeZone(focusZoneRect, m_zones.size()); if (zone) { AddZone(zone); @@ -645,7 +672,7 @@ bool ZoneSet::CalculateColumnsAndRowsLayout(Rect workArea, FancyZonesDataTypes:: } - auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size() + 1); + auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size()); if (zone) { AddZone(zone); @@ -713,9 +740,9 @@ bool ZoneSet::CalculateGridLayout(Rect workArea, FancyZonesDataTypes::ZoneSetLay } int index = 0; - for (int col = columns - 1; col >= 0; col--) + for (int row = 0; row < rows; row++) { - for (int row = rows - 1; row >= 0; row--) + for (int col = 0; col < columns; col++) { gridLayoutInfo.cellChildMap()[row][col] = index++; if (index == zoneCount) @@ -765,7 +792,7 @@ bool ZoneSet::CalculateCustomLayout(Rect workArea, int spacing) noexcept DPIAware::Convert(m_config.Monitor, x, y); DPIAware::Convert(m_config.Monitor, width, height); - auto zone = MakeZone(RECT{ x, y, x + width, y + height }, m_zones.size() + 1); + auto zone = MakeZone(RECT{ x, y, x + width, y + height }, m_zones.size()); if (zone) { AddZone(zone); @@ -848,7 +875,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutI long right = columnInfo[maxCol].End; long bottom = rowInfo[maxRow].End; - auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size() + 1); + auto zone = MakeZone(RECT{ left, top, right, bottom }, i); if (zone) { AddZone(zone); @@ -873,30 +900,32 @@ std::vector ZoneSet::GetCombinedZoneRange(const std::vector& ini RECT boundingRect; bool boundingRectEmpty = true; - auto zones = GetZones(); for (size_t zoneId : combinedZones) { - const RECT& rect = zones[zoneId]->GetZoneRect(); - if (boundingRectEmpty) + if (m_zones.contains(zoneId)) { - boundingRect = rect; - boundingRectEmpty = false; - } - else - { - boundingRect.left = min(boundingRect.left, rect.left); - boundingRect.top = min(boundingRect.top, rect.top); - boundingRect.right = max(boundingRect.right, rect.right); - boundingRect.bottom = max(boundingRect.bottom, rect.bottom); + const RECT rect = m_zones.at(zoneId)->GetZoneRect(); + if (boundingRectEmpty) + { + boundingRect = rect; + boundingRectEmpty = false; + } + else + { + boundingRect.left = min(boundingRect.left, rect.left); + boundingRect.top = min(boundingRect.top, rect.top); + boundingRect.right = max(boundingRect.right, rect.right); + boundingRect.bottom = max(boundingRect.bottom, rect.bottom); + } } } if (!boundingRectEmpty) { - for (size_t zoneId = 0; zoneId < zones.size(); zoneId++) + for (const auto& [zoneId, zone] : m_zones) { - RECT rect = zones[zoneId]->GetZoneRect(); + const RECT rect = zone->GetZoneRect(); if (boundingRect.left <= rect.left && rect.right <= boundingRect.right && boundingRect.top <= rect.top && rect.bottom <= boundingRect.bottom) { diff --git a/src/modules/fancyzones/lib/ZoneSet.h b/src/modules/fancyzones/lib/ZoneSet.h index cc99cac07c..48f4de4afe 100644 --- a/src/modules/fancyzones/lib/ZoneSet.h +++ b/src/modules/fancyzones/lib/ZoneSet.h @@ -6,13 +6,15 @@ namespace FancyZonesDataTypes { enum class ZoneSetLayoutType; } - /** * Class representing single zone layout. ZoneSet is responsible for actual calculation of rectangle coordinates * (whether is grid or canvas layout) and moving windows through them. */ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : public IUnknown { + // Mapping zone id to zone + using ZonesMap = std::map>; + /** * @returns Unique identifier of zone layout. */ @@ -45,7 +47,7 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : /** * @returns Array of zone objects (defining coordinates of the zone) inside this zone layout. */ - IFACEMETHOD_(std::vector>, GetZones)() const = 0; + IFACEMETHOD_(ZonesMap, GetZones) () const = 0; /** * Assign window to the zone based on zone index inside zone layout. * diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index abdad6e7bf..09499186f1 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -69,7 +69,7 @@ namespace ZoneWindowUtils COLORREF hostZoneBorderColor, COLORREF hostZoneHighlightColor, int hostZoneHighlightOpacity, - std::vector> zones, + IZoneSet::ZonesMap zones, std::vector highlightZone, bool flashMode) { @@ -621,7 +621,7 @@ void ZoneWindow::OnPaint(HDC hdc) noexcept COLORREF hostZoneBorderColor{}; COLORREF hostZoneHighlightColor{}; int hostZoneHighlightOpacity{}; - std::vector> zones{}; + IZoneSet::ZonesMap zones; std::vector highlightZone = m_highlightZone; bool flashMode = m_flashMode; diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp b/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp index 2e3275b8d4..e47951eab2 100644 --- a/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp +++ b/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp @@ -1,7 +1,10 @@ #include "pch.h" #include "ZoneWindowDrawing.h" +#include +#include #include +#include namespace NonLocalizable { @@ -79,7 +82,7 @@ namespace g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush); } - void DrawZone(wil::unique_hdc& hdc, ZoneWindowDrawing::ColorSetting const& colorSetting, winrt::com_ptr zone, const std::vector>& zones, bool flashMode) noexcept + void DrawZone(wil::unique_hdc& hdc, ZoneWindowDrawing::ColorSetting const& colorSetting, winrt::com_ptr zone, bool flashMode)noexcept { RECT zoneRect = zone->GetZoneRect(); @@ -95,7 +98,7 @@ namespace if (!flashMode) { - DrawIndex(hdc, zoneRect, zone->Id()); + DrawIndex(hdc, zoneRect, zone->Id() + 1); } } } @@ -112,7 +115,7 @@ namespace ZoneWindowDrawing COLORREF zoneBorderColor, COLORREF highlightColor, int zoneOpacity, - const std::vector>& zones, + const IZoneSet::ZonesMap& zones, const std::vector& highlightZones, bool flashMode) noexcept { @@ -121,52 +124,41 @@ namespace ZoneWindowDrawing ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 }; ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 }; - std::vector isHighlighted(zones.size(), false); - for (size_t x : highlightZones) - { - isHighlighted[x] = true; - } - // First draw the inactive zones for (auto iter = zones.begin(); iter != zones.end(); iter++) { - int zoneId = static_cast(iter - zones.begin()); - winrt::com_ptr zone = iter->try_as(); + winrt::com_ptr zone = iter->second; + size_t zoneId = zone->Id(); if (!zone) { continue; } - if (!isHighlighted[zoneId]) + auto zoneIt = std::find(highlightZones.begin(), highlightZones.end(), zoneId); + if (zoneIt == highlightZones.end()) { if (flashMode) { - DrawZone(hdc, colorFlash, zone, zones, flashMode); + DrawZone(hdc, colorFlash, zone, flashMode); } else { colorViewer.fill = zoneColor; colorViewer.border = zoneBorderColor; - DrawZone(hdc, colorViewer, zone, zones, flashMode); + DrawZone(hdc, colorViewer, zone, flashMode); } } } // Draw the active zones on top of the inactive zones - for (auto iter = zones.begin(); iter != zones.end(); iter++) + for (const auto& zoneId : highlightZones) { - int zoneId = static_cast(iter - zones.begin()); - winrt::com_ptr zone = iter->try_as(); - if (!zone) - { - continue; - } + colorHighlight.fill = highlightColor; + colorHighlight.border = zoneBorderColor; - if (isHighlighted[zoneId]) + if (zones.contains(zoneId)) { - colorHighlight.fill = highlightColor; - colorHighlight.border = zoneBorderColor; - DrawZone(hdc, colorHighlight, zone, zones, flashMode); + DrawZone(hdc, colorHighlight, zones.at(zoneId), flashMode); } } } diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.h b/src/modules/fancyzones/lib/ZoneWindowDrawing.h index 3b0e31fefc..35932a57b9 100644 --- a/src/modules/fancyzones/lib/ZoneWindowDrawing.h +++ b/src/modules/fancyzones/lib/ZoneWindowDrawing.h @@ -1,11 +1,13 @@ #pragma once +#include #include #include #include #include "util.h" #include "Zone.h" +#include "ZoneSet.h" namespace ZoneWindowDrawing { @@ -24,7 +26,7 @@ namespace ZoneWindowDrawing COLORREF zoneBorderColor, COLORREF highlightColor, int zoneOpacity, - const std::vector>& zones, + const IZoneSet::ZonesMap& zones, const std::vector& highlightZones, bool flashMode) noexcept; } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 93aabe4d72..1841255049 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -85,28 +85,28 @@ namespace FancyZonesUnitTests TEST_METHOD (AddOne) { - constexpr size_t zoneId = 1; + constexpr size_t zoneId = 0; winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); Assert::IsNotNull(zone.get()); m_set->AddZone(zone); auto zones = m_set->GetZones(); Assert::AreEqual((size_t)1, zones.size()); - compareZones(zone, zones[0]); - Assert::AreEqual(zoneId, zones[0]->Id()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); } TEST_METHOD (AddManyEqual) { for (size_t i = 0; i < 1024; i++) { - size_t zoneId = i + 1; + size_t zoneId = i; winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); Assert::IsNotNull(zone.get()); m_set->AddZone(zone); auto zones = m_set->GetZones(); Assert::AreEqual(i + 1, zones.size()); - compareZones(zone, zones[i]); - Assert::AreEqual(zoneId, zones[i]->Id()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); } } @@ -114,7 +114,7 @@ namespace FancyZonesUnitTests { for (size_t i = 0; i < 1024; i++) { - size_t zoneId = i + 1; + size_t zoneId = i; int left = rand() % 10; int top = rand() % 10; int right = left + 1 + rand() % 100; @@ -124,8 +124,8 @@ namespace FancyZonesUnitTests m_set->AddZone(zone); auto zones = m_set->GetZones(); Assert::AreEqual(i + 1, zones.size()); - compareZones(zone, zones[i]); - Assert::AreEqual(zoneId, zones[i]->Id()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); } } @@ -135,13 +135,6 @@ namespace FancyZonesUnitTests Assert::IsNotNull(zone.get()); } - TEST_METHOD (MakeZoneWithInvalidId) - { - constexpr size_t invalidZoneId = 0; - winrt::com_ptr zone = MakeZone({ 0, 0, 0, 0 }, invalidZoneId); - Assert::IsNull(zone.get()); - } - TEST_METHOD (MakeZoneFromInvalidRectWidth) { winrt::com_ptr zone = MakeZone({ 100, 100, 99, 101 }, 1); @@ -361,9 +354,9 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameWindow) { // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 2); - winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 3); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); m_set->AddZone(zone1); m_set->AddZone(zone2); m_set->AddZone(zone3); @@ -382,9 +375,9 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameIndex) { // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 2); - winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 3); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); m_set->AddZone(zone1); m_set->AddZone(zone2); m_set->AddZone(zone3); @@ -414,7 +407,7 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByPointInnerPoint) { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); m_set->AddZone(zone1); auto window = Mocks::Window(); @@ -425,8 +418,8 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByPointInnerPointOverlappingZones) { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); m_set->AddZone(zone1); m_set->AddZone(zone2); @@ -441,8 +434,8 @@ namespace FancyZonesUnitTests const auto window = Mocks::Window(); const auto zoneWindow = Mocks::Window(); - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); m_set->AddZone(zone1); m_set->AddZone(zone2); @@ -459,8 +452,8 @@ namespace FancyZonesUnitTests const auto window = Mocks::Window(); const auto zoneWindow = Mocks::Window(); - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); @@ -477,9 +470,9 @@ namespace FancyZonesUnitTests const auto window = Mocks::Window(); const auto zoneWindow = Mocks::Window(); - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); - winrt::com_ptr zone3 = MakeZone({ 20, 20, 80, 80 }, 3); + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); + winrt::com_ptr zone3 = MakeZone({ 20, 20, 80, 80 }, 2); m_set->AddZone(zone1); m_set->AddZone(zone2); @@ -507,9 +500,9 @@ namespace FancyZonesUnitTests m_set = MakeZoneSet(config); // Add a couple of zones. - m_zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_zone2 = MakeZone({ 0, 0, 100, 100 }, 2); - m_zone3 = MakeZone({ 0, 0, 100, 100 }, 3); + m_zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + m_zone2 = MakeZone({ 0, 0, 100, 100 }, 1); + m_zone3 = MakeZone({ 0, 0, 100, 100 }, 2); m_set->AddZone(m_zone1); m_set->AddZone(m_zone2); m_set->AddZone(m_zone3); @@ -776,7 +769,7 @@ namespace FancyZonesUnitTests { Assert::IsTrue(set->IsZoneEmpty(zoneId)); - const auto& zoneRect = zone->GetZoneRect(); + const auto& zoneRect = zone.second->GetZoneRect(); Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); From e5f281021277e402ffd409344f52de8c8841aaf6 Mon Sep 17 00:00:00 2001 From: Arjun Balgovind <32061677+arjunbalgovind@users.noreply.github.com> Date: Mon, 26 Oct 2020 08:56:34 -0700 Subject: [PATCH 13/22] Add dev docs for Localization (#7481) * Added localization doc * Update * Added details for C++ projects * Added table of contents * Added more info about C++ and C# loc * Update localization.md * Update localization.md * Update localization.md --- .pipelines/pipeline.user.windows.yml | 2 +- doc/devdocs/localization.md | 173 +++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 doc/devdocs/localization.md diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 007a9a0bca..d40e6b72e1 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -41,7 +41,7 @@ restore: build: commands: - # Localize the files after the build procedure to avoid existing localized files from getting overwritten. To be moved before the Build PowerToys step once the lcl files have been checked in. Tracked at https://github.com/microsoft/PowerToys/issues/6046 + # Localize the files before the Build PowerToys step to generate translated resx files from the lcl files - !!buildcommand name: 'Localize Power Toys' command: '.pipelines\build-localization.cmd' diff --git a/doc/devdocs/localization.md b/doc/devdocs/localization.md new file mode 100644 index 0000000000..ef6d305067 --- /dev/null +++ b/doc/devdocs/localization.md @@ -0,0 +1,173 @@ +# Localization + +## Table of Contents +1. [Localization on the pipeline (CDPX)](#localization-on-the-pipeline-cdpx) + 1. [UWP Special case](#uwp-special-case) +2. [Enabling localization on a new project](#enabling-localization-on-a-new-project) + 1. [C++](#c) + 2. [C#](#c-1) + 3. [UWP](#uwp) +3. [Lcl Files](#lcl-files) +4. [Possible Issues in localization PRs (LEGO)](#possible-issues-in-localization-prs-lego) +5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project) + +## Localization on the pipeline (CDPX) +[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/master/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package. + +The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/master/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail [here](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules. + +Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/master/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipelinhttps://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this. + +The process and variables that can be tweaked on the pipeline are described in more detail [here](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization). + +The localized resource dlls for C# projects are added to the MSI only for build on the pipeline. This is done by checking if the [`IsPipeline` variable is defined](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L804-L805), which gets defined before building the installer on the pipeline [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/build-installer.cmd#L4). This is done because the localized resx files are only present on the pipeline, and not having this check would result in the installer project failing to build locally. + +### UWP Special case +C# projects normally expect localized resource files with the language id in the file name as Resources.`langId`.resx, where `langId` is generally a two character code except for language with specific variants (like zh-Hans or pt-BR): + +For example, `path\Resources.resx` for English and `path\Resources.fr.resx` for French. + +UWP differs from this as it expects the resources to have the same Resources.resw file name, but they should be present in language specific folders, with the full language ID (such as fr-fr, zh-hans, pt-br, etc.) + +For example, `path\en-us\Resources.resw` for English and `path\fr-fr\Resources.resw` for French. + +Since the pipeline generates it in this format, [a script is run](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L29-L31) to move these resw files to the correct format expected by all UWP projects. Currently the only UWP project is [Microsoft.PowerToys.Settings.UI](https://github.com/microsoft/PowerToys/tree/master/src/core/Microsoft.PowerToys.Settings.UI). The script used for moving the resources can be [found here](https://github.com/microsoft/PowerToys/blob/master/tools/localization/move_uwp_resources.ps1). The equivalent full language IDs for each shortened language ID obtained from the pipeline has been hardcoded in the script. + +## Enabling localization on a new project +To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root. + +For example, for a project in the folder `src\path` where the resx file is present in `resources\Resources.resx`, the LocProject.json file will contain the following: +``` +{ + "Projects": [ + { + "LanguageSet": "Azure_Languages", + "LocItems": [ + { + "SourceFile": "src\\path\\resources\\Resources.resx", + "CopyOption": "LangIDOnName", + "OutputPath": "src\\path\\resources" + } + ] + } + ] +} +``` +The rest of the steps depend on the project type and are covered in the sections below. The steps to add the localized files to the MSI can be found [here](#Enabling-localized-MSI-for-a-new-project). + +### C++ +C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project. + +If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files. + +After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event: +``` + + + +``` + +This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script can be found [here](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format: +``` +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +STRINGTABLE +BEGIN + strings +END + +#endif +``` +Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` values from each langId from the pipeline, these are hardcoded in the script (for each language) as done [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/tools/build/convert-resx-to-rc.ps1#L50-L77). **If any other languages are added in the future, this script will have to be updated.** In order to determine what are the language codes, you can open the rc file in Resource View, right click the string table and press `Insert Copy` and choose the corresponding language. This autogenerates the required code and can be used to figure out the language codes. The files also add the resource declarations to a resource.h file, starting from 101 by default(this can be changed by an optional argument). Since the output files will be generated in `Generated Files`, any includes in these two files will require an additional `..\` and wherever resource.h is used, it will have to be included as `Generated Files\resource.h`. While adding `resource.base.h` and `ProjName.base.rc` to the vcxproj, these should be modified to not participate in the build to avoid build errors: +``` + +``` + +Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/Directory.Build.targets) for an example. + +Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project. + +### C# +Since C# projects natively support `resx` files, the only step required here is to include all the resx files in the build. For .NET Core projects this is done automatically and the .csproj does not need to be modified. For other projects, the following line needs to be added: +``` + +``` + +**Note:** Building with localized resources may cause a build warning `Referenced assembly 'mscorlib.dll' targets a different processor` which is a VS bug. More details can be found [here](https://github.com/microsoft/PowerToys/issues/7269). + +**Note:** If a project needs to be migrated from XAML resources to resx, the easiest way to convert the resources would be to change to format to `=` separates resources by either manually (by Ctrl+H on a text editor), or by a script, and then running [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) on `Developer Command Prompt for VS` to convert it to resx format. +``` +Calculator +Allows to do mathematical calculations.(Try 5*3-2 in Wox) +Not a number (NaN) +``` +to +``` +wox_plugin_calculator_plugin_name=Calculator +wox_plugin_calculator_plugin_description=Allows to do mathematical calculations.(Try 5*3-2 in Wox) +wox_plugin_calculator_not_a_number=Not a number (NaN) +``` +After adding the resx file to the project along with the resource generator, references to the strings will have to be replaced with `Properties.Resources.resName` rather than the custom APIs. Check [this PR](https://github.com/microsoft/PowerToys/pull/6165) for an example of the changes required. + +### UWP +UWP projects expect `resw` files rather than `resx` (the format is almost the same). Unlike other C# projects, the files are expected in the format `fullLangId\Resources.resw`. To include these files in the build, replace the following line in the csproj: +``` + +``` +to +``` + +``` + +## Lcl Files +Lcl files contain all the resources that are present in the English resx file, along with a translation if it has been added. + +For example, an entry for a resource in the lcl file looks like this: +``` + + + + + + + + + +``` +The `` element would not be present in the initial commits of the lcl files, as only the English version of the string would be present. + +**Note:** The CDPX Localization system has a fail-safe check on the lcl files, where if the English string value which is present inside `` does not match the value present in the English Resources.resx file then the translated value will not be copied to the localized resx file. This is present so that obsolete translations would not be loaded when the English resource has changed, and the English string will be used rather than the obsolete translation. + +## Possible Issues in localization PRs (LEGO) +Since the LEGO PRs update some of the strings in LCL files at a time, there can be multiple PRs which modify the same files, leading to merge conflicts. In most cases this would show up on GitHub as a merge conflict, but sometimes a bad git merge may occur, and the file could end up with incorrect formatting, such as two `` elements for a single resource. These can be fixed by ensuring the elements follow the format described in [this section](#lcl-files). To catch such errors, the build farm should be run for every LEGO PR and if any error occurs in the localization step, we should check the corresponding resx/lcl files for conflicts. + +## Enabling localized MSI for a new project +For C++ and UWP projects no additional files are generated with localization that need to be added to the MSI. For C++ projects all the resources are added to the dll/exe, while for UWP projects they are added to the `resources.pri` file (which is present even for an unlocalized project). To verify if the localized resources are added to the `resources.pri` file the following steps can be done: +- Open `Developer Command Prompt for VS` +- After navigating to the folder containing the pri file, run the following command: + + makepri.exe dump /if .\resources.pri +- Check the contents of the `resources.pri.xml` file that is generated from the command. The last section of the file will contain the resources with the strings in all the languages: +``` + + + Running as administrator + + + Running as administrator + + +``` + +For C# projects, satellite dlls are generated when the project is built. For a project named `ProjName`, files are created in the format `langId\ProjName.resources.dll` where `langId` is in the same format as the lcl files. The satellite dlls need to be included with the MSI, but they must be added only if the solution is built from the build farm, as the localized resx files will not be present on local machines (and that could cause local builds of the installer to fail). +This can be done by adding the directory name of the project [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L806) and a resource component for the project can be created [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L845-L847) in this format: +``` + + + +``` + +We should also ensure the new dlls are signed by the pipeline. Currently all dlls of the form [`*.resources.dll` are signed](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/pipeline.user.windows.yml#L68). + +**Note:** The resource dlls should be added to the MSI project only after the initial commit with the lcl files has been done by the Localization team. Otherwise the pipeline will fail as there wouldn't be any resx files to generate the dlls. From 8ca0c3524210f4059f31564f514db2ff69d24463 Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Fri, 23 Oct 2020 18:40:23 +0300 Subject: [PATCH 14/22] FZ: fix nullptr crash --- src/modules/fancyzones/lib/FancyZones.cpp | 54 +++++++++++++---------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 44d02f0d58..fcbe723584 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -1180,21 +1180,24 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept else { auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); - auto zoneSet = workArea->ActiveZoneSet(); - if (zoneSet) + if (workArea) { - const auto zones = zoneSet->GetZones(); - for (const auto& [zoneId, zone] : zones) + auto zoneSet = workArea->ActiveZoneSet(); + if (zoneSet) { - RECT zoneRect = zone->GetZoneRect(); + const auto zones = zoneSet->GetZones(); + for (const auto& [zoneId, zone] : zones) + { + RECT zoneRect = zone->GetZoneRect(); - zoneRect.left += monitorRect.left; - zoneRect.right += monitorRect.left; - zoneRect.top += monitorRect.top; - zoneRect.bottom += monitorRect.top; + zoneRect.left += monitorRect.left; + zoneRect.right += monitorRect.left; + zoneRect.top += monitorRect.top; + zoneRect.bottom += monitorRect.top; - zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(zoneId, workArea); + zoneRects.emplace_back(zoneRect); + zoneRectsInfo.emplace_back(zoneId, workArea); + } } } } @@ -1224,21 +1227,24 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept if (currentMonitorRect.top <= currentMonitorRect.bottom) { auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); - auto zoneSet = workArea->ActiveZoneSet(); - if (zoneSet) + if (workArea) { - const auto zones = zoneSet->GetZones(); - for (const auto& [zoneId, zone] : zones) + auto zoneSet = workArea->ActiveZoneSet(); + if (zoneSet) { - RECT zoneRect = zone->GetZoneRect(); + const auto zones = zoneSet->GetZones(); + for (const auto& [zoneId, zone] : zones) + { + RECT zoneRect = zone->GetZoneRect(); - zoneRect.left += currentMonitorRect.left; - zoneRect.right += currentMonitorRect.left; - zoneRect.top += currentMonitorRect.top; - zoneRect.bottom += currentMonitorRect.top; + zoneRect.left += currentMonitorRect.left; + zoneRect.right += currentMonitorRect.left; + zoneRect.top += currentMonitorRect.top; + zoneRect.bottom += currentMonitorRect.top; - zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(zoneId, workArea); + zoneRects.emplace_back(zoneRect); + zoneRectsInfo.emplace_back(zoneId, workArea); + } } } } @@ -1351,9 +1357,9 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept { monitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTONULL); } - + auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); - if (zoneWindow->ActiveZoneSet() != nullptr) + if (zoneWindow && zoneWindow->ActiveZoneSet() != nullptr) { if (vkCode == VK_UP || vkCode == VK_DOWN) { From 129342edffd8906e57acf023cd4f0eb8fa9e88fd Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Mon, 26 Oct 2020 19:23:50 +0300 Subject: [PATCH 15/22] FZ: format FancyZones.cpp --- src/modules/fancyzones/lib/FancyZones.cpp | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index fcbe723584..1d8cb6c7e3 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -616,7 +616,7 @@ void FancyZones::ToggleEditor() noexcept winrt::com_ptr zoneWindow; std::shared_lock readLock(m_lock); - + if (m_settings->GetSettings()->spanZonesAcrossMonitors) { zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, NULL); @@ -625,7 +625,7 @@ void FancyZones::ToggleEditor() noexcept { zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); } - + if (!zoneWindow) { return; @@ -639,7 +639,8 @@ void FancyZones::ToggleEditor() noexcept m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); - } }).wait(); + } }) + .wait(); UINT currentDpi = 0; for (const auto& monitor : allMonitors) @@ -650,15 +651,15 @@ void FancyZones::ToggleEditor() noexcept { if (currentDpi == 0) { - currentDpi = dpiX; - continue; + currentDpi = dpiX; + continue; } if (currentDpi != dpiX) { MessageBoxW(NULL, - GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(), - GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - MB_OK | MB_ICONWARNING); + GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(), + GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + MB_OK | MB_ICONWARNING); break; } } @@ -693,8 +694,9 @@ void FancyZones::ToggleEditor() noexcept mi.cbSize = sizeof(mi); m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { - GetMonitorInfo(monitor, &mi); - } }).wait(); + GetMonitorInfo(monitor, &mi); + } }) + .wait(); const auto x = mi.rcWork.left; const auto y = mi.rcWork.top; @@ -981,7 +983,7 @@ void FancyZones::UpdateZoneWindows() noexcept auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { capture* params = reinterpret_cast(data); - MONITORINFOEX mi{ { .cbSize = sizeof(mi)} }; + MONITORINFOEX mi{ { .cbSize = sizeof(mi) } }; if (GetMonitorInfoW(monitor, &mi)) { auto& displayDeviceIdxMap = *(params->displayDeviceIdx); @@ -1006,8 +1008,8 @@ void FancyZones::UpdateZoneWindows() noexcept if (deviceId.empty()) { deviceId = GetSystemMetrics(SM_REMOTESESSION) ? - L"\\\\?\\DISPLAY#REMOTEDISPLAY#" : - L"\\\\?\\DISPLAY#LOCALDISPLAY#"; + L"\\\\?\\DISPLAY#REMOTEDISPLAY#" : + L"\\\\?\\DISPLAY#LOCALDISPLAY#"; fancyZones->AddZoneWindow(monitor, deviceId); } From ec22bc40bc4d3e2bbaf80614fe792a659373184c Mon Sep 17 00:00:00 2001 From: Arjun Balgovind <32061677+arjunbalgovind@users.noreply.github.com> Date: Mon, 26 Oct 2020 11:06:35 -0700 Subject: [PATCH 16/22] Switch focus to last Image Size ListViewItem on adding new size (#7505) --- .../ViewModels/ImageResizerViewModel.cs | 5 +++++ .../Views/ImageResizerPage.xaml | 3 ++- .../Views/ImageResizerPage.xaml.cs | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ImageResizerViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ImageResizerViewModel.cs index fcede542e8..a875bef97b 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ImageResizerViewModel.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ImageResizerViewModel.cs @@ -86,6 +86,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private bool _keepDateModified; private int _encoderGuidId; + public bool IsListViewFocusRequested { get; set; } + public bool IsEnabled { get @@ -257,6 +259,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels imageSizes.Add(newSize); _advancedSizes = imageSizes; SavesImageSizes(imageSizes); + + // Set the focus requested flag to indicate that an add operation has occurred during the ContainerContentChanging event + IsListViewFocusRequested = true; } public void DeleteImageSize(int id) diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml index 5032456db1..c43e3a974f 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml @@ -65,7 +65,8 @@ SelectionMode="None" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" - ScrollViewer.IsHorizontalRailEnabled="True"> + ScrollViewer.IsHorizontalRailEnabled="True" + ContainerContentChanging="ImagesSizesListView_ContainerContentChanging">