From b8e5ccfb7ba92ab3751d65a06cd7fe00f3d4d582 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Tue, 17 Nov 2020 11:38:19 +0300 Subject: [PATCH] [FancyZones] Editor multi monitor support (#6562) Co-authored-by: Enrico Giordani Co-authored-by: Enrico Giordani --- .github/actions/spell-check/expect.txt | 1 + .../fancyzones/editor/FancyZonesEditor.sln | 4 +- .../editor/FancyZonesEditor/App.config | 3 + .../editor/FancyZonesEditor/App.xaml.cs | 101 ++- .../editor/FancyZonesEditor/CanvasEditor.xaml | 6 +- .../FancyZonesEditor/CanvasEditor.xaml.cs | 8 +- .../FancyZonesEditor/CanvasEditorWindow.xaml | 2 +- .../CanvasEditorWindow.xaml.cs | 18 +- .../FancyZonesEditor/CanvasZone.xaml.cs | 8 +- .../ModelToVisibilityConverter.xaml.cs | 2 +- .../FancyZonesEditor/EditorOverlay.xaml.cs | 137 --- .../editor/FancyZonesEditor/EditorWindow.cs | 8 +- .../FancyZonesEditor/FancyZonesEditor.csproj | 36 +- .../editor/FancyZonesEditor/GridData.cs | 1 + .../editor/FancyZonesEditor/GridEditor.xaml | 58 +- .../FancyZonesEditor/GridEditor.xaml.cs | 31 +- .../FancyZonesEditor/GridEditorWindow.xaml | 2 +- .../FancyZonesEditor/GridEditorWindow.xaml.cs | 5 +- .../editor/FancyZonesEditor/GridZone.xaml.cs | 8 +- ...rOverlay.xaml => LayoutOverlayWindow.xaml} | 7 +- .../LayoutOverlayWindow.xaml.cs | 21 + .../FancyZonesEditor/LayoutPreview.xaml.cs | 68 +- .../editor/FancyZonesEditor/MainWindow.xaml | 788 +++++++++++------- .../FancyZonesEditor/MainWindow.xaml.cs | 111 +-- .../Models/CanvasLayoutModel.cs | 82 +- .../editor/FancyZonesEditor/Models/Device.cs | 81 ++ .../Models/GridLayoutModel.cs | 20 +- .../FancyZonesEditor/Models/LayoutModel.cs | 362 ++------ .../FancyZonesEditor/Models/LayoutSettings.cs | 33 + .../FancyZonesEditor/Models/LayoutType.cs | 17 + ...Settings.cs => MainWindowSettingsModel.cs} | 390 ++++----- .../editor/FancyZonesEditor/Models/Monitor.cs | 58 ++ .../Models/MonitorInfoModel.cs | 86 ++ .../editor/FancyZonesEditor/Overlay.cs | 397 +++++++++ .../Properties/Resources.Designer.cs | 81 ++ .../Properties/Resources.resx | 27 + .../FancyZonesEditor/Utils/EventArgs`1.cs | 18 + .../FancyZonesEditor/Utils/EventRaiser.cs | 32 + .../Utils/FancyZonesEditorIO.cs | 678 +++++++++++++++ .../Utils/MonitorChangedEventArgs.cs | 18 + .../FancyZonesEditor/Utils/RelayCommand.cs | 67 ++ .../FancyZonesEditor/Utils/RelayCommand`1.cs | 48 ++ .../ViewModels/MonitorViewModel.cs | 81 ++ .../editor/FancyZonesEditor/app.manifest | 74 ++ src/modules/fancyzones/lib/FancyZones.cpp | 166 ++-- src/modules/fancyzones/lib/FancyZonesData.cpp | 32 +- src/modules/fancyzones/lib/FancyZonesData.h | 5 +- .../fancyzones/lib/FancyZonesDataTypes.h | 9 + src/modules/fancyzones/lib/JsonHelpers.cpp | 101 ++- src/modules/fancyzones/lib/JsonHelpers.h | 19 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 38 - src/modules/fancyzones/lib/ZoneWindow.h | 6 - src/modules/fancyzones/lib/util.cpp | 157 +++- src/modules/fancyzones/lib/util.h | 36 +- .../tests/UnitTests/JsonHelpers.Tests.cpp | 140 +++- .../fancyzones/tests/UnitTests/Util.Spec.cpp | 123 ++- .../fancyzones/tests/UnitTests/Util.cpp | 11 + src/modules/fancyzones/tests/UnitTests/Util.h | 1 + .../tests/UnitTests/ZoneSet.Spec.cpp | 39 +- .../tests/UnitTests/ZoneWindow.Spec.cpp | 55 +- .../App.xaml | 9 + .../App.xaml.cs | 17 + .../AssemblyInfo.cs | 10 + .../FancyZonesEditor_DPI_netcore_test.csproj | 15 + .../FancyZonesEditor_DPI_netcore_test.sln | 25 + .../MainWindow.xaml | 15 + .../MainWindow.xaml.cs | 120 +++ .../MonitorsInfo.cs | 156 ++++ .../OverlayWindow.xaml | 14 + .../OverlayWindow.xaml.cs | 25 + .../app.manifest | 74 ++ .../FancyZonesEditor_DPI_test.sln | 25 + .../FancyZonesEditor_DPI_test/App.config | 9 + .../FancyZonesEditor_DPI_test/App.xaml | 9 + .../FancyZonesEditor_DPI_test/App.xaml.cs | 17 + .../FancyZonesEditor_DPI_test.csproj | 144 ++++ .../FancyZonesEditor_DPI_test/MainWindow.xaml | 15 + .../MainWindow.xaml.cs | 119 +++ .../FancyZonesEditor_DPI_test/MonitorInfo.cs | 156 ++++ .../OverlayWindow.xaml | 14 + .../OverlayWindow.xaml.cs | 22 + .../Properties/AssemblyInfo.cs | 55 ++ .../Properties/Resources.Designer.cs | 71 ++ .../Properties/Resources.resx | 117 +++ .../Properties/Settings.Designer.cs | 30 + .../Properties/Settings.settings | 7 + .../FancyZonesEditor_DPI_test/app.manifest | 74 ++ .../FancyZonesEditor_DPI_test/packages.config | 4 + 88 files changed, 4887 insertions(+), 1503 deletions(-) delete mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs rename src/modules/fancyzones/editor/FancyZonesEditor/{EditorOverlay.xaml => LayoutOverlayWindow.xaml} (73%) create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Models/Device.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutSettings.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs rename src/modules/fancyzones/editor/FancyZonesEditor/Models/{Settings.cs => MainWindowSettingsModel.cs} (56%) create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Models/Monitor.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Models/MonitorInfoModel.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventArgs`1.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventRaiser.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/MonitorChangedEventArgs.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand`1.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/ViewModels/MonitorViewModel.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/app.manifest create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/App.xaml create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/App.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/AssemblyInfo.cs create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.csproj create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.sln create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/MonitorsInfo.cs create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_netcore_test/app.manifest create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.sln create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.config create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.csproj create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MonitorInfo.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/AssemblyInfo.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.Designer.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.resx create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.Designer.cs create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.settings create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/app.manifest create mode 100644 tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/packages.config diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ac50aa86bd..04a6cac6c9 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1456,6 +1456,7 @@ NDEBUG ndp neq NESW +netcore netcoreapp netframework netfx diff --git a/src/modules/fancyzones/editor/FancyZonesEditor.sln b/src/modules/fancyzones/editor/FancyZonesEditor.sln index 421bb54260..fe4d33c9f7 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor.sln +++ b/src/modules/fancyzones/editor/FancyZonesEditor.sln @@ -13,8 +13,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.ActiveCfg = Debug|x64 {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.Build.0 = Debug|x64 - {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Debug|x64 - {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Debug|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Release|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.config b/src/modules/fancyzones/editor/FancyZonesEditor/App.config index de277144ee..c34d9ed0d9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.config +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.config @@ -3,4 +3,7 @@ + + + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index 751fc6c40a..e7486c401a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -1,18 +1,17 @@ -// Copyright (c) Microsoft Corporation +// 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; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Abstractions; using System.Linq; -using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using System.Windows; -using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; using ManagedCommon; namespace FancyZonesEditor @@ -24,6 +23,7 @@ namespace FancyZonesEditor { // Non-localizable strings private const string CrashReportLogFile = "FZEditorCrashLog.txt"; + private const string ErrorReportLogFile = "FZEditorErrorLog.txt"; private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug"; private const string CrashReportExceptionTag = "Exception"; @@ -44,57 +44,76 @@ namespace FancyZonesEditor private readonly IFileSystem _fileSystem = new FileSystem(); - public Settings ZoneSettings { get; } + public MainWindowSettingsModel MainWindowSettings { get; } + + public static FancyZonesEditorIO FancyZonesEditorIO { get; private set; } + + public static Overlay Overlay { get; private set; } + + public static int PowerToysPID { get; set; } + + public static bool DebugMode + { + get + { + return _debugMode; + } + } + + private static bool _debugMode = false; + + [Conditional("DEBUG")] + private void DebugModeCheck() + { + _debugMode = true; + } public App() { - ZoneSettings = new Settings(); + DebugModeCheck(); + FancyZonesEditorIO = new FancyZonesEditorIO(); + Overlay = new Overlay(); + MainWindowSettings = new MainWindowSettingsModel(); } private void OnStartup(object sender, StartupEventArgs e) { AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; - RunnerHelper.WaitForPowerToysRunner(Settings.PowerToysPID, () => + RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () => { Environment.Exit(0); }); - LayoutModel foundModel = null; + FancyZonesEditorIO.ParseCommandLineArguments(); + FancyZonesEditorIO.ParseDeviceInfoData(); - foreach (LayoutModel model in ZoneSettings.DefaultModels) + MainWindowSettingsModel settings = ((App)Current).MainWindowSettings; + settings.UpdateSelectedLayoutModel(); + + Overlay.Show(); + } + + public static void ShowExceptionMessageBox(string message, Exception exception = null) + { + string fullMessage = FancyZonesEditor.Properties.Resources.Error_Report + PowerToysIssuesURL + " \n" + message; + if (exception != null) { - if (model.Type == Settings.ActiveZoneSetLayoutType) - { - // found match - foundModel = model; - break; - } + fullMessage += ": " + exception.Message; } - if (foundModel == null) - { - foreach (LayoutModel model in Settings.CustomModels) - { - if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper()) - { - // found match - foundModel = model; - break; - } - } - } + MessageBox.Show(fullMessage, FancyZonesEditor.Properties.Resources.Error_Exception_Message_Box_Title); + } - if (foundModel == null) - { - foundModel = ZoneSettings.DefaultModels[0]; - } + public static void ShowExceptionReportMessageBox(string reportData) + { + var fileStream = File.OpenWrite(ErrorReportLogFile); + var sw = new StreamWriter(fileStream); + sw.Write(reportData); + sw.Flush(); + fileStream.Close(); - foundModel.IsSelected = true; - - EditorOverlay overlay = new EditorOverlay(); - overlay.Show(); - overlay.DataContext = foundModel; + ShowReportMessageBox(fileStream.Name); } private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) @@ -103,16 +122,22 @@ namespace FancyZonesEditor var sw = new StreamWriter(fileStream); sw.Write(FormatException((Exception)args.ExceptionObject)); fileStream.Close(); + + ShowReportMessageBox(fileStream.Name); + } + + private static void ShowReportMessageBox(string fileName) + { MessageBox.Show( FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part1 + - Path.GetFullPath(fileStream.Name) + + Path.GetFullPath(fileName) + "\n" + FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part2 + PowerToysIssuesURL, FancyZonesEditor.Properties.Resources.Fancy_Zones_Editor_App_Title); } - private string FormatException(Exception ex) + private static string FormatException(Exception ex) { var sb = new StringBuilder(); sb.AppendLine(); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml index 27321f82cc..a1e82be18e 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml @@ -5,7 +5,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - - + + + + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml.cs index 76bf222c55..767b56f811 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditor.xaml.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation +// 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.Windows; using System.Windows.Controls; using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; namespace FancyZonesEditor { @@ -46,6 +47,10 @@ namespace FancyZonesEditor private void UpdateZoneRects() { + var workArea = App.Overlay.WorkArea; + Preview.Width = workArea.Width; + Preview.Height = workArea.Height; + UIElementCollection previewChildren = Preview.Children; int previewChildrenCount = previewChildren.Count; while (previewChildrenCount < _model.Zones.Count) @@ -54,6 +59,7 @@ namespace FancyZonesEditor { Model = _model, }; + Preview.Children.Add(zone); previewChildrenCount++; } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml index 3650a644f6..a7eb95c12e 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml @@ -13,7 +13,7 @@ SizeToContent="Height" Background="White" ResizeMode="NoResize" - WindowStartupLocation="CenterScreen" + WindowStartupLocation="CenterOwner" Closed="OnClosed"> diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs index 356cb7b177..994f1a54d4 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasEditorWindow.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -19,24 +19,28 @@ namespace FancyZonesEditor KeyUp += CanvasEditorWindow_KeyUp; - _model = EditorOverlay.Current.DataContext as CanvasLayoutModel; + _model = App.Overlay.CurrentDataContext as CanvasLayoutModel; _stashedModel = (CanvasLayoutModel)_model.Clone(); } private void OnAddZone(object sender, RoutedEventArgs e) { - if (_offset + (int)(Settings.WorkArea.Width * 0.4) < (int)Settings.WorkArea.Width - && _offset + (int)(Settings.WorkArea.Height * 0.4) < (int)Settings.WorkArea.Height) + Rect workingArea = App.Overlay.WorkArea; + int offset = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(_offset); + + if (offset + (int)(workingArea.Width * 0.4) < (int)workingArea.Width + && offset + (int)(workingArea.Height * 0.4) < (int)workingArea.Height) { - _model.AddZone(new Int32Rect(_offset, _offset, (int)(Settings.WorkArea.Width * 0.4), (int)(Settings.WorkArea.Height * 0.4))); + _model.AddZone(new Int32Rect(offset, offset, (int)(workingArea.Width * 0.4), (int)(workingArea.Height * 0.4))); } else { _offset = 100; - _model.AddZone(new Int32Rect(_offset, _offset, (int)(Settings.WorkArea.Width * 0.4), (int)(Settings.WorkArea.Height * 0.4))); + offset = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(_offset); + _model.AddZone(new Int32Rect(offset, offset, (int)(workingArea.Width * 0.4), (int)(workingArea.Height * 0.4))); } - _offset += 50; + _offset += 50; // TODO: replace hardcoded numbers } protected new void OnCancel(object sender, RoutedEventArgs e) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs index 91c892217a..2678ccd077 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs @@ -8,6 +8,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; namespace FancyZonesEditor { @@ -84,7 +85,7 @@ namespace FancyZonesEditor } } - foreach (Rect singleMonitor in Settings.UsedWorkAreas) + foreach (Rect singleMonitor in App.Overlay.WorkAreas) { int monitorPositionLow = (int)(isX ? singleMonitor.Left : singleMonitor.Top); int monitorPositionHigh = (int)(isX ? singleMonitor.Right : singleMonitor.Bottom); @@ -213,8 +214,9 @@ namespace FancyZonesEditor private SnappyHelperBase NewDefaultSnappyHelper(bool isX, ResizeMode mode) { - int screenAxisOrigin = (int)(isX ? Settings.WorkArea.Left : Settings.WorkArea.Top); - int screenAxisSize = (int)(isX ? Settings.WorkArea.Width : Settings.WorkArea.Height); + Rect workingArea = App.Overlay.WorkArea; + int screenAxisOrigin = (int)(isX ? workingArea.Left : workingArea.Top); + int screenAxisSize = (int)(isX ? workingArea.Width : workingArea.Height); return new SnappyHelperMagnetic(Model.Zones, ZoneIndex, isX, mode, screenAxisOrigin, screenAxisSize); } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Converters/ModelToVisibilityConverter.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Converters/ModelToVisibilityConverter.xaml.cs index fee4cfb5b9..4d01967384 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Converters/ModelToVisibilityConverter.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Converters/ModelToVisibilityConverter.xaml.cs @@ -13,7 +13,7 @@ namespace FancyZonesEditor.Converters { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - return Settings.IsPredefinedLayout((LayoutModel)value) ? Visibility.Collapsed : Visibility.Visible; + return MainWindowSettingsModel.IsPredefinedLayout((LayoutModel)value) ? Visibility.Collapsed : Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs deleted file mode 100644 index 008ffec2b8..0000000000 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs +++ /dev/null @@ -1,137 +0,0 @@ -// 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.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using FancyZonesEditor.Models; - -namespace FancyZonesEditor -{ - /// - /// Interaction logic for EditorOverlay.xaml - /// - public partial class EditorOverlay : Window - { - public static EditorOverlay Current { get; set; } - - private readonly Settings _settings = ((App)Application.Current).ZoneSettings; - private LayoutPreview _layoutPreview; - - private UserControl _editor; - - private static MainWindow _mainWindow = new MainWindow(); - - public Int32Rect[] GetZoneRects() - { - if (_editor != null) - { - if (_editor is GridEditor gridEditor) - { - return ZoneRectsFromPanel(gridEditor.PreviewPanel); - } - else - { - // CanvasEditor - return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview); - } - } - else - { - // One of the predefined zones (neither grid or canvas editor used). - return _layoutPreview.GetZoneRects(); - } - } - - private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel) - { - // TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info - int count = previewPanel.Children.Count; - Int32Rect[] zones = new Int32Rect[count]; - - for (int i = 0; i < count; i++) - { - FrameworkElement child = (FrameworkElement)previewPanel.Children[i]; - Point topLeft = child.TransformToAncestor(previewPanel).Transform(default); - - zones[i].X = (int)topLeft.X; - zones[i].Y = (int)topLeft.Y; - zones[i].Width = (int)child.ActualWidth; - zones[i].Height = (int)child.ActualHeight; - } - - return zones; - } - - public EditorOverlay() - { - InitializeComponent(); - Current = this; - - Left = Settings.WorkArea.Left; - Top = Settings.WorkArea.Top; - Width = Settings.WorkArea.Width; - Height = Settings.WorkArea.Height; - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - ShowLayoutPicker(); - } - - public void ShowLayoutPicker() - { - _editor = null; - _layoutPreview = new LayoutPreview - { - IsActualSize = true, - Opacity = 0.5, - }; - - Content = _layoutPreview; - - _mainWindow.Owner = this; - _mainWindow.ShowActivated = true; - _mainWindow.Topmost = true; - _mainWindow.Show(); - _mainWindow.LeftWindowCommands = null; - _mainWindow.RightWindowCommands = null; - - // window is set to topmost to make sure it shows on top of PowerToys settings page - // we can reset topmost flag now - _mainWindow.Topmost = false; - } - - // These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard - // They reflect that current state into properties on the Settings object, which the Zone view will listen to in editing mode - protected override void OnPreviewKeyDown(KeyEventArgs e) - { - _settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift); - _settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control); - base.OnPreviewKeyDown(e); - } - - protected override void OnPreviewKeyUp(KeyEventArgs e) - { - _settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift); - _settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control); - base.OnPreviewKeyUp(e); - } - - public void Edit() - { - _layoutPreview = null; - if (DataContext is GridLayoutModel) - { - _editor = new GridEditor(); - } - else if (DataContext is CanvasLayoutModel) - { - _editor = new CanvasEditor(); - } - - Content = _editor; - } - } -} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs index 7f1f6a7951..50cd457980 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs @@ -13,8 +13,8 @@ namespace FancyZonesEditor { protected void OnSaveApplyTemplate(object sender, RoutedEventArgs e) { - EditorOverlay mainEditor = EditorOverlay.Current; - if (mainEditor.DataContext is LayoutModel model) + var mainEditor = App.Overlay; + if (mainEditor.CurrentDataContext is LayoutModel model) { // If new custom Canvas layout is created (i.e. edited Blank layout), // it's type needs to be updated @@ -30,14 +30,14 @@ namespace FancyZonesEditor _backToLayoutPicker = false; Close(); - EditorOverlay.Current.Close(); + mainEditor.CloseEditor(); } protected void OnClosed(object sender, EventArgs e) { if (_backToLayoutPicker) { - EditorOverlay.Current.ShowLayoutPicker(); + App.Overlay.CloseEditor(); } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj index 6bf62391a1..211435d465 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj +++ b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj @@ -89,6 +89,9 @@ images\FancyZonesEditor.ico + + app.manifest + @@ -118,9 +121,16 @@ GlobalSuppressions.cs + + + LayoutOverlayWindow.xaml + + + + @@ -140,10 +150,7 @@ - - - EditorOverlay.xaml - + @@ -151,6 +158,15 @@ GridEditorWindow.xaml + + + + + + + + + WindowLayout.xaml @@ -175,6 +191,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -191,10 +211,6 @@ MainWindow.xaml Code - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -235,6 +251,7 @@ Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs @@ -253,6 +270,9 @@ 12.2.5 + + 6.6.30107 + 4.7.2 diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs index 9e61428498..9610b50b68 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; namespace FancyZonesEditor { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml index 14b57f53cc..09a039c116 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml @@ -19,33 +19,35 @@ - - - - + + + + + - - - - - + + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs index 9b71ede841..1171ad7aaf 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs @@ -8,6 +8,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; namespace FancyZonesEditor { @@ -32,7 +33,7 @@ namespace FancyZonesEditor InitializeComponent(); Loaded += GridEditor_Loaded; Unloaded += GridEditor_Unloaded; - ((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged; + ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; gridEditorUniqueId = ++gridEditorUniqueIdCounter; } @@ -69,7 +70,8 @@ namespace FancyZonesEditor private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { - Size actualSize = new Size(ActualWidth, ActualHeight); + Rect workingArea = App.Overlay.WorkArea; + Size actualSize = new Size(workingArea.Width, workingArea.Height); // Only enter if this is the newest instance if (actualSize.Width > 0 && gridEditorUniqueId == gridEditorUniqueIdCounter) @@ -248,7 +250,7 @@ namespace FancyZonesEditor } _dragHandles.UpdateAfterVerticalSplit(foundCol); - _data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, ActualWidth); + _data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Width); _dragHandles.AddDragHandle(Orientation.Vertical, foundRow, foundCol, model); } else @@ -298,11 +300,12 @@ namespace FancyZonesEditor } _dragHandles.UpdateAfterHorizontalSplit(foundRow); - _data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, ActualHeight); + _data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Height); _dragHandles.AddDragHandle(Orientation.Horizontal, foundRow, foundCol, model); } - Size actualSize = new Size(ActualWidth, ActualHeight); + var workArea = App.Overlay.WorkArea; + Size actualSize = new Size(workArea.Width, workArea.Height); ArrangeGridRects(actualSize); } @@ -354,7 +357,8 @@ namespace FancyZonesEditor private void OnGridDimensionsChanged() { - Size actualSize = new Size(ActualWidth, ActualHeight); + Rect workingArea = App.Overlay.WorkArea; + Size actualSize = new Size(workingArea.Width, workingArea.Height); if (actualSize.Width > 0) { ArrangeGridRects(actualSize); @@ -363,6 +367,10 @@ namespace FancyZonesEditor private void ArrangeGridRects(Size arrangeSize) { + var workArea = App.Overlay.WorkArea; + Preview.Width = workArea.Width; + Preview.Height = workArea.Height; + GridLayoutModel model = Model; if (model == null) { @@ -375,7 +383,7 @@ namespace FancyZonesEditor return; } - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; int spacing = settings.ShowSpacing ? settings.Spacing : 0; @@ -403,7 +411,7 @@ namespace FancyZonesEditor if (_dragHandles.HasSnappedNonAdjacentResizers(resizer)) { double spacing = 0; - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; if (settings.ShowSpacing) { spacing = settings.Spacing; @@ -422,7 +430,8 @@ namespace FancyZonesEditor } } - Size actualSize = new Size(ActualWidth, ActualHeight); + Rect workingArea = App.Overlay.WorkArea; + Size actualSize = new Size(workingArea.Width, workingArea.Height); ArrangeGridRects(actualSize); AdornerLayer.UpdateLayout(); } @@ -433,7 +442,8 @@ namespace FancyZonesEditor int index = _data.SwappedIndexAfterResize(resizer); if (index != -1) { - Size actualSize = new Size(ActualWidth, ActualHeight); + Rect workingArea = App.Overlay.WorkArea; + Size actualSize = new Size(workingArea.Width, workingArea.Height); ArrangeGridRects(actualSize); } } @@ -478,7 +488,6 @@ namespace FancyZonesEditor if (_startDragPos.X != -1) { Point dragPos = e.GetPosition(Preview); - _startRow = -1; _endRow = -1; _startCol = -1; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml index 4699192a39..5a35cf349b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml @@ -13,7 +13,7 @@ SizeToContent="Height" Background="White" ResizeMode="NoResize" - WindowStartupLocation="CenterScreen" + WindowStartupLocation="CenterOwner" Closed="OnClosed"> diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs index fe66febc90..417bbc61cc 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Input; using FancyZonesEditor.Models; +using FancyZonesEditor.Utils; namespace FancyZonesEditor { @@ -19,13 +20,13 @@ namespace FancyZonesEditor KeyUp += GridEditorWindow_KeyUp; - _stashedModel = (GridLayoutModel)(EditorOverlay.Current.DataContext as GridLayoutModel).Clone(); + _stashedModel = (GridLayoutModel)(App.Overlay.CurrentDataContext as GridLayoutModel).Clone(); } protected new void OnCancel(object sender, RoutedEventArgs e) { base.OnCancel(sender, e); - GridLayoutModel model = EditorOverlay.Current.DataContext as GridLayoutModel; + GridLayoutModel model = App.Overlay.CurrentDataContext as GridLayoutModel; _stashedModel.RestoreTo(model); } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs index a87768cbd2..837a6f2ab0 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs @@ -69,14 +69,14 @@ namespace FancyZonesEditor }; Body.Children.Add(_splitter); - ((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged; + ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; } private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == PropertyIsShiftKeyPressedID) { - _switchOrientation = ((App)Application.Current).ZoneSettings.IsShiftKeyPressed; + _switchOrientation = ((App)Application.Current).MainWindowSettings.IsShiftKeyPressed; if (_lastPos.X != -1) { UpdateSplitter(); @@ -108,7 +108,7 @@ namespace FancyZonesEditor { get { - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; if (!settings.ShowSpacing) { return 1; @@ -283,7 +283,7 @@ namespace FancyZonesEditor private void DoSplit(Orientation orientation, double offset) { int spacing = 0; - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; if (settings.ShowSpacing) { spacing = settings.Spacing; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml similarity index 73% rename from src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml rename to src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml index 19c79f6d8c..2ccd224aa2 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml @@ -1,14 +1,13 @@ - + Background="Transparent"/> \ No newline at end of file diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml.cs new file mode 100644 index 0000000000..0f5e8e762b --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutOverlayWindow.xaml.cs @@ -0,0 +1,21 @@ +// 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; +using System.Windows; +using System.Windows.Media; + +namespace FancyZonesEditor +{ + /// + /// Interaction logic for LayoutOverlayWindow.xaml + /// + public partial class LayoutOverlayWindow : Window + { + public LayoutOverlayWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs index 6bd4037b0d..0ff7585fc7 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -29,11 +29,22 @@ namespace FancyZonesEditor private LayoutModel _model; private List _zones = new List(); + public bool IsActualSize + { + get { return (bool)GetValue(IsActualSizeProperty); } + set { SetValue(IsActualSizeProperty, value); } + } + public LayoutPreview() { InitializeComponent(); DataContextChanged += LayoutPreview_DataContextChanged; - ((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged; + ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; + } + + public void UpdatePreview() + { + RenderPreview(); } private void LayoutPreview_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) @@ -42,12 +53,6 @@ namespace FancyZonesEditor RenderPreview(); } - public bool IsActualSize - { - get { return (bool)GetValue(IsActualSizeProperty); } - set { SetValue(IsActualSizeProperty, value); } - } - private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == PropertyZoneCountID) @@ -109,27 +114,25 @@ namespace FancyZonesEditor RowColInfo[] colInfo = (from percent in grid.ColumnPercents select new RowColInfo(percent)).ToArray(); - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; int spacing = settings.ShowSpacing ? settings.Spacing : 0; - int width = (int)Settings.WorkArea.Width; - int height = (int)Settings.WorkArea.Height; - - double totalWidth = width - (spacing * (cols + 1)); - double totalHeight = height - (spacing * (rows + 1)); + var workArea = App.Overlay.WorkArea; + double width = workArea.Width - (spacing * (cols + 1)); + double height = workArea.Height - (spacing * (rows + 1)); double top = spacing; for (int row = 0; row < rows; row++) { - double cellHeight = rowInfo[row].Recalculate(top, totalHeight); + double cellHeight = rowInfo[row].Recalculate(top, height); top += cellHeight + spacing; } double left = spacing; for (int col = 0; col < cols; col++) { - double cellWidth = colInfo[col].Recalculate(left, totalWidth); + double cellWidth = colInfo[col].Recalculate(left, width); left += cellWidth + spacing; } @@ -140,8 +143,8 @@ namespace FancyZonesEditor Body.Children.Add(viewbox); Canvas frame = new Canvas { - Width = width, - Height = height, + Width = workArea.Width, + Height = workArea.Height, }; viewbox.Child = frame; @@ -183,6 +186,14 @@ namespace FancyZonesEditor } } } + + if (App.DebugMode) + { + TextBlock text = new TextBlock(); + text.Text = "(" + workArea.X + "," + workArea.Y + ")"; + text.FontSize = 42; + frame.Children.Add(text); + } } private void RenderSmallScalePreview(GridLayoutModel grid) @@ -205,7 +216,7 @@ namespace FancyZonesEditor Body.ColumnDefinitions.Add(def); } - Settings settings = ((App)Application.Current).ZoneSettings; + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; Thickness margin = new Thickness(settings.ShowSpacing ? settings.Spacing / 20 : 0); List visited = new List(); @@ -265,6 +276,12 @@ namespace FancyZonesEditor private void RenderCanvasPreview(CanvasLayoutModel canvas) { + var workArea = canvas.CanvasRect; + if (workArea.Width == 0 || workArea.Height == 0 || App.Overlay.SpanZonesAcrossMonitors) + { + workArea = App.Overlay.WorkArea; + } + Viewbox viewbox = new Viewbox { Stretch = Stretch.Uniform, @@ -272,10 +289,11 @@ namespace FancyZonesEditor Body.Children.Add(viewbox); Canvas frame = new Canvas { - Width = Settings.WorkArea.Width, - Height = Settings.WorkArea.Height, + Width = workArea.Width, + Height = workArea.Height, }; viewbox.Child = frame; + foreach (Int32Rect zone in canvas.Zones) { Rectangle rect = new Rectangle(); @@ -288,6 +306,14 @@ namespace FancyZonesEditor rect.Fill = Brushes.LightGray; frame.Children.Add(rect); } + + if (App.DebugMode) + { + TextBlock text = new TextBlock(); + text.Text = "(" + App.Overlay.WorkArea.X + "," + App.Overlay.WorkArea.Y + ")"; + text.FontSize = 42; + frame.Children.Add(text); + } } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml index c20e72d03a..ac48c2b37e 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml @@ -1,324 +1,464 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index 4a94e08d7a..e036dc8bd8 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -19,12 +18,16 @@ namespace FancyZonesEditor { // TODO: share the constants b/w C# Editor and FancyZoneLib public const int MaxZones = 40; - private readonly Settings _settings = ((App)Application.Current).ZoneSettings; + private const int DefaultWrapPanelItemSize = 262; + private const int SmallWrapPanelItemSize = 180; + private const int MinimalForDefaultWrapPanelsHeight = 900; + + private readonly MainWindowSettingsModel _settings = ((App)Application.Current).MainWindowSettings; // Localizable string private static readonly string _defaultNamePrefix = "Custom Layout "; - public int WrapPanelItemSize { get; set; } = 262; + public int WrapPanelItemSize { get; set; } = DefaultWrapPanelItemSize; public double SettingsTextMaxWidth { @@ -34,20 +37,31 @@ namespace FancyZonesEditor } } - public MainWindow() + public MainWindow(bool spanZonesAcrossMonitors, Rect workArea) { InitializeComponent(); DataContext = _settings; KeyUp += MainWindow_KeyUp; - if (Settings.WorkArea.Height < 900) + if (spanZonesAcrossMonitors) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + + if (workArea.Height < MinimalForDefaultWrapPanelsHeight || App.Overlay.MultiMonitorMode) { SizeToContent = SizeToContent.WidthAndHeight; - WrapPanelItemSize = 180; + WrapPanelItemSize = SmallWrapPanelItemSize; } } + public void Update() + { + DataContext = _settings; + SetSelectedItem(); + } + private void MainWindow_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.Escape) @@ -101,19 +115,19 @@ namespace FancyZonesEditor private void Select(LayoutModel newSelection) { - if (EditorOverlay.Current.DataContext is LayoutModel currentSelection) + if (App.Overlay.CurrentDataContext is LayoutModel currentSelection) { currentSelection.IsSelected = false; } newSelection.IsSelected = true; - EditorOverlay.Current.DataContext = newSelection; + App.Overlay.CurrentDataContext = newSelection; } private void EditLayout_Click(object sender, RoutedEventArgs e) { - EditorOverlay mainEditor = EditorOverlay.Current; - if (!(mainEditor.DataContext is LayoutModel model)) + var mainEditor = App.Overlay; + if (!(mainEditor.CurrentDataContext is LayoutModel model)) { return; } @@ -121,19 +135,19 @@ namespace FancyZonesEditor model.IsSelected = false; Hide(); - bool isPredefinedLayout = Settings.IsPredefinedLayout(model); + bool isPredefinedLayout = MainWindowSettingsModel.IsPredefinedLayout(model); - if (!Settings.CustomModels.Contains(model) || isPredefinedLayout) + if (!MainWindowSettingsModel.CustomModels.Contains(model) || isPredefinedLayout) { if (isPredefinedLayout) { // make a copy model = model.Clone(); - mainEditor.DataContext = model; + mainEditor.CurrentDataContext = model; } int maxCustomIndex = 0; - foreach (LayoutModel customModel in Settings.CustomModels) + foreach (LayoutModel customModel in MainWindowSettingsModel.CustomModels) { string name = customModel.Name; if (name.StartsWith(_defaultNamePrefix)) @@ -151,32 +165,7 @@ namespace FancyZonesEditor model.Name = _defaultNamePrefix + (++maxCustomIndex); } - mainEditor.Edit(); - - EditorWindow window; - bool isGrid = false; - if (model is GridLayoutModel) - { - window = new GridEditorWindow(); - isGrid = true; - } - else - { - window = new CanvasEditorWindow(); - } - - window.Owner = EditorOverlay.Current; - - window.DataContext = model; - window.Show(); - - if (isGrid) - { - (window as GridEditorWindow).NameTextBox().Focus(); - } - - window.LeftWindowCommands = null; - window.RightWindowCommands = null; + mainEditor.OpenEditor(model); } private void Apply_Click(object sender, RoutedEventArgs e) @@ -186,20 +175,16 @@ namespace FancyZonesEditor private void Apply() { - EditorOverlay mainEditor = EditorOverlay.Current; + ((App)Application.Current).MainWindowSettings.ResetAppliedModel(); - if (mainEditor.DataContext is LayoutModel model) + var mainEditor = App.Overlay; + if (mainEditor.CurrentDataContext is LayoutModel model) { - // If custom canvas layout has been scaled, persisting is needed - if (model is CanvasLayoutModel && (model as CanvasLayoutModel).IsScaled) - { - model.Persist(); - } - else - { - model.Apply(); - } + model.Apply(); + } + if (!mainEditor.MultiMonitorMode) + { Close(); } } @@ -207,7 +192,7 @@ namespace FancyZonesEditor private void OnClosing(object sender, EventArgs e) { LayoutModel.SerializeDeletedCustomZoneSets(); - EditorOverlay.Current.Close(); + App.Overlay.CloseLayoutWindow(); } private void OnInitialized(object sender, EventArgs e) @@ -217,7 +202,7 @@ namespace FancyZonesEditor private void SetSelectedItem() { - foreach (LayoutModel model in Settings.CustomModels) + foreach (LayoutModel model in MainWindowSettingsModel.CustomModels) { if (model.IsSelected) { @@ -237,5 +222,25 @@ namespace FancyZonesEditor model.Delete(); } + + private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + ScrollViewer scrollviewer = sender as ScrollViewer; + if (e.Delta > 0) + { + scrollviewer.LineLeft(); + } + else + { + scrollviewer.LineRight(); + } + + e.Handled = true; + } + + private void CloseButton_Click(object sender, RoutedEventArgs e) + { + this.Close(); + } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs index fecda82b32..3af612ddfb 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs @@ -1,10 +1,9 @@ -// Copyright (c) Microsoft Corporation +// 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; using System.Collections.Generic; -using System.IO.Abstractions; using System.Text.Json; using System.Windows; @@ -14,33 +13,21 @@ namespace FancyZonesEditor.Models // Free form Layout Model, which specifies independent zone rects public class CanvasLayoutModel : LayoutModel { - // Localizable strings - private const string ErrorPersistingCanvasLayout = "Error persisting canvas layout"; - // Non-localizable strings private const string ModelTypeID = "canvas"; - public CanvasLayoutModel(string uuid, string name, LayoutType type, IList zones, int workAreaWidth, int workAreaHeight) + public Rect CanvasRect { get; private set; } + + public CanvasLayoutModel(string uuid, string name, LayoutType type, IList zones, int width, int height) : base(uuid, name, type) { - lastWorkAreaWidth = workAreaWidth; - lastWorkAreaHeight = workAreaHeight; - IsScaled = false; - - if (ShouldScaleLayout()) - { - ScaleLayout(zones); - } - else - { - Zones = zones; - } + Zones = zones; + CanvasRect = new Rect(new Size(width, height)); } public CanvasLayoutModel(string name, LayoutType type) : base(name, type) { - IsScaled = false; } public CanvasLayoutModel(string name) @@ -51,12 +38,6 @@ namespace FancyZonesEditor.Models // Zones - the list of all zones in this layout, described as independent rectangles public IList Zones { get; private set; } = new List(); - private int lastWorkAreaWidth = (int)Settings.WorkArea.Width; - - private int lastWorkAreaHeight = (int)Settings.WorkArea.Height; - - public bool IsScaled { get; private set; } - // RemoveZoneAt // Removes the specified index from the Zones list, and fires a property changed notification for the Zones property public void RemoveZoneAt(int index) @@ -102,34 +83,6 @@ namespace FancyZonesEditor.Models } } - private bool ShouldScaleLayout() - { - // Scale if: - // - at least one dimension changed - // - orientation remained the same - return (lastWorkAreaHeight != Settings.WorkArea.Height || lastWorkAreaWidth != Settings.WorkArea.Width) && - ((lastWorkAreaHeight > lastWorkAreaWidth && Settings.WorkArea.Height > Settings.WorkArea.Width) || - (lastWorkAreaWidth > lastWorkAreaHeight && Settings.WorkArea.Width > Settings.WorkArea.Height)); - } - - private void ScaleLayout(IList zones) - { - foreach (Int32Rect zone in zones) - { - double widthFactor = (double)Settings.WorkArea.Width / lastWorkAreaWidth; - double heightFactor = (double)Settings.WorkArea.Height / lastWorkAreaHeight; - int scaledX = (int)(zone.X * widthFactor); - int scaledY = (int)(zone.Y * heightFactor); - int scaledWidth = (int)(zone.Width * widthFactor); - int scaledHeight = (int)(zone.Height * heightFactor); - Zones.Add(new Int32Rect(scaledX, scaledY, scaledWidth, scaledHeight)); - } - - lastWorkAreaHeight = (int)Settings.WorkArea.Height; - lastWorkAreaWidth = (int)Settings.WorkArea.Width; - IsScaled = true; - } - private struct Zone { public int X { get; set; } @@ -165,13 +118,15 @@ namespace FancyZonesEditor.Models // Implements the LayoutModel.PersistData abstract method protected override void PersistData() { + AddCustomLayout(this); + CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo { - RefWidth = lastWorkAreaWidth, - RefHeight = lastWorkAreaHeight, - + RefWidth = (int)CanvasRect.Width, + RefHeight = (int)CanvasRect.Height, Zones = new Zone[Zones.Count], }; + for (int i = 0; i < Zones.Count; ++i) { Zone zone = new Zone @@ -187,26 +142,19 @@ namespace FancyZonesEditor.Models CanvasLayoutJson jsonObj = new CanvasLayoutJson { - Uuid = "{" + Guid.ToString().ToUpper() + "}", + Uuid = Uuid, Name = Name, Type = ModelTypeID, Info = layoutInfo, }; - JsonSerializerOptions options = new JsonSerializerOptions { PropertyNamingPolicy = new DashCaseNamingPolicy(), }; - try - { - string jsonString = JsonSerializer.Serialize(jsonObj, options); - FileSystem.File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString); - } - catch (Exception ex) - { - ShowExceptionMessageBox(ErrorPersistingCanvasLayout, ex); - } + string jsonString = JsonSerializer.Serialize(jsonObj, options); + AddCustomLayoutJson(JsonSerializer.Deserialize(jsonString)); + SerializeCreatedCustomZonesets(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Device.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Device.cs new file mode 100644 index 0000000000..00031f170f --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Device.cs @@ -0,0 +1,81 @@ +// 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; +using System.Text; +using System.Windows; + +namespace FancyZonesEditor.Utils +{ + public class Device + { + public string Id { get; set; } + + public Rect UnscaledBounds { get; private set; } + + public Rect ScaledBounds { get; private set; } + + public Rect WorkAreaRect { get; private set; } + + public int Dpi { get; set; } + + public bool Primary { get; private set; } + + public Device(string id, int dpi, Rect bounds, Rect workArea, bool primary) + { + Id = id; + Dpi = dpi; + WorkAreaRect = workArea; + UnscaledBounds = bounds; + ScaledBounds = bounds; + Primary = primary; + } + + public Device(Rect bounds, Rect workArea, bool primary) + { + WorkAreaRect = workArea; + UnscaledBounds = bounds; + ScaledBounds = bounds; + Primary = primary; + } + + public void Scale(double scaleFactor) + { + WorkAreaRect = new Rect(Math.Round(WorkAreaRect.X * scaleFactor), Math.Round(WorkAreaRect.Y * scaleFactor), Math.Round(WorkAreaRect.Width * scaleFactor), Math.Round(WorkAreaRect.Height * scaleFactor)); + ScaledBounds = new Rect(Math.Round(ScaledBounds.X * scaleFactor), Math.Round(ScaledBounds.Y * scaleFactor), Math.Round(ScaledBounds.Width * scaleFactor), Math.Round(ScaledBounds.Height * scaleFactor)); + } + + public double ScaleCoordinate(double coordinate) + { + float dpi = Dpi != 0 ? Dpi : 96f; + double scaleFactor = 96f / dpi; + return Math.Round(coordinate * scaleFactor); + } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.Append("ID: "); + sb.AppendLine(Id); + sb.Append("DPI: "); + sb.AppendLine(Dpi.ToString()); + sb.Append("Is primary: "); + sb.AppendLine(Primary.ToString()); + + string workArea = string.Format("({0}, {1}, {2}, {3})", WorkAreaRect.X, WorkAreaRect.Y, WorkAreaRect.Width, WorkAreaRect.Height); + string bounds = string.Format("({0}, {1}, {2}, {3})", UnscaledBounds.X, UnscaledBounds.Y, UnscaledBounds.Width, UnscaledBounds.Height); + string scaledBounds = string.Format("({0}, {1}, {2}, {3})", ScaledBounds.X, ScaledBounds.Y, ScaledBounds.Width, ScaledBounds.Height); + + sb.Append("Work area: "); + sb.AppendLine(workArea); + sb.Append("Unscaled bounds: "); + sb.AppendLine(bounds); + sb.Append("Scaled bounds: "); + sb.AppendLine(scaledBounds); + + return sb.ToString(); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index abcd3b2de9..1dbc391e13 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -13,9 +13,6 @@ namespace FancyZonesEditor.Models // Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans public class GridLayoutModel : LayoutModel { - // Localizable strings - private const string ErrorPersistingGridLayout = "Error persisting grid layout"; - // Non-localizable strings private const string ModelTypeID = "grid"; @@ -204,6 +201,8 @@ namespace FancyZonesEditor.Models // Implements the LayoutModel.PersistData abstract method protected override void PersistData() { + AddCustomLayout(this); + GridLayoutInfo layoutInfo = new GridLayoutInfo { Rows = Rows, @@ -212,6 +211,7 @@ namespace FancyZonesEditor.Models ColumnsPercentage = ColumnPercents, CellChildMap = new int[Rows][], }; + for (int row = 0; row < Rows; row++) { layoutInfo.CellChildMap[row] = new int[Columns]; @@ -223,7 +223,7 @@ namespace FancyZonesEditor.Models GridLayoutJson jsonObj = new GridLayoutJson { - Uuid = "{" + Guid.ToString().ToUpper() + "}", + Uuid = Uuid, Name = Name, Type = ModelTypeID, Info = layoutInfo, @@ -233,15 +233,9 @@ namespace FancyZonesEditor.Models PropertyNamingPolicy = new DashCaseNamingPolicy(), }; - try - { - string jsonString = JsonSerializer.Serialize(jsonObj, options); - FileSystem.File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString); - } - catch (Exception ex) - { - ShowExceptionMessageBox(ErrorPersistingGridLayout, ex); - } + string jsonString = JsonSerializer.Serialize(jsonObj, options); + AddCustomLayoutJson(JsonSerializer.Deserialize(jsonString)); + SerializeCreatedCustomZonesets(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 1a286505ea..f921afb5d2 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -6,77 +6,15 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; -using System.IO.Abstractions; -using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; -using System.Windows; namespace FancyZonesEditor.Models { - public enum LayoutType - { - Blank = -1, - Focus, - Columns, - Rows, - Grid, - PriorityGrid, - Custom, - } - // Base LayoutModel // Manages common properties and base persistence public abstract class LayoutModel : INotifyPropertyChanged { - protected static readonly IFileSystem FileSystem = new FileSystem(); - - // Localizable strings - private const string ErrorMessageBoxTitle = "FancyZones Editor Exception Handler"; - private const string ErrorMessageBoxMessage = "Please report the bug to "; - private const string ErrorLayoutMalformedData = "Layout '{0}' has malformed data"; - private const string ErrorSerializingDeletedLayouts = "Error serializing deleted layouts"; - private const string ErrorLoadingCustomLayouts = "Error loading custom layouts"; - private const string ErrorApplyingLayout = "Error applying layout"; - - // Non-localizable strings - private const string NameStr = "name"; - private const string CustomZoneSetsJsonTag = "custom-zone-sets"; - private const string TypeJsonTag = "type"; - private const string UuidJsonTag = "uuid"; - private const string InfoJsonTag = "info"; - private const string GridJsonTag = "grid"; - private const string RowsJsonTag = "rows"; - private const string ColumnsJsonTag = "columns"; - private const string RowsPercentageJsonTag = "rows-percentage"; - private const string ColumnsPercentageJsonTag = "columns-percentage"; - private const string CellChildMapJsonTag = "cell-child-map"; - private const string ZonesJsonTag = "zones"; - private const string CanvasJsonTag = "canvas"; - private const string RefWidthJsonTag = "ref-width"; - private const string RefHeightJsonTag = "ref-height"; - private const string XJsonTag = "X"; - private const string YJsonTag = "Y"; - private const string WidthJsonTag = "width"; - private const string HeightJsonTag = "height"; - private const string FocusJsonTag = "focus"; - private const string PriorityGridJsonTag = "priority-grid"; - private const string CustomJsonTag = "custom"; - - private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug"; - - public static void ShowExceptionMessageBox(string message, Exception exception = null) - { - string fullMessage = ErrorMessageBoxMessage + PowerToysIssuesURL + " \n" + message; - if (exception != null) - { - fullMessage += ": " + exception.Message; - } - - MessageBox.Show(fullMessage, ErrorMessageBoxTitle); - } - protected LayoutModel() { _guid = Guid.NewGuid(); @@ -136,6 +74,14 @@ namespace FancyZonesEditor.Models private Guid _guid; + public string Uuid + { + get + { + return "{" + Guid.ToString().ToUpper() + "}"; + } + } + // IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker // TODO: once we switch to a picker per monitor, we need to move this state to the view public bool IsSelected @@ -157,6 +103,25 @@ namespace FancyZonesEditor.Models private bool _isSelected; + public bool IsApplied + { + get + { + return _isApplied; + } + + set + { + if (_isApplied != value) + { + _isApplied = value; + FirePropertyChanged(); + } + } + } + + private bool _isApplied; + // implementation of INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; @@ -177,186 +142,52 @@ namespace FancyZonesEditor.Models } } - private struct DeletedCustomZoneSetsWrapper + // Adds new custom Layout + public void AddCustomLayout(LayoutModel model) { - public List DeletedCustomZoneSets { get; set; } + bool updated = false; + for (int i = 0; i < _customModels.Count && !updated; i++) + { + if (_customModels[i].Uuid == model.Uuid) + { + _customModels[i] = model; + updated = true; + } + } + + if (!updated) + { + _customModels.Add(model); + } + } + + // Add custom layouts json data that would be serialized to a temp file + public void AddCustomLayoutJson(JsonElement json) + { + _createdCustomLayouts.Add(json); } public static void SerializeDeletedCustomZoneSets() { - DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper - { - DeletedCustomZoneSets = _deletedCustomModels, - }; + App.FancyZonesEditorIO.SerializeDeletedCustomZoneSets(_deletedCustomModels); + } - JsonSerializerOptions options = new JsonSerializerOptions - { - PropertyNamingPolicy = new DashCaseNamingPolicy(), - }; - - try - { - string jsonString = JsonSerializer.Serialize(deletedLayouts, options); - FileSystem.File.WriteAllText(Settings.DeletedCustomZoneSetsTmpFile, jsonString); - } - catch (Exception ex) - { - ShowExceptionMessageBox(ErrorSerializingDeletedLayouts, ex); - } + public static void SerializeCreatedCustomZonesets() + { + App.FancyZonesEditorIO.SerializeCreatedCustomZonesets(_createdCustomLayouts); } // Loads all the custom Layouts from tmp file passed by FancyZonesLib public static ObservableCollection LoadCustomModels() { _customModels = new ObservableCollection(); - - try - { - Stream inputStream = FileSystem.File.Open(Settings.FancyZonesSettingsFile, FileMode.Open); - JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default); - JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray(); - - while (customZoneSetsEnumerator.MoveNext()) - { - var current = customZoneSetsEnumerator.Current; - string name = current.GetProperty(NameStr).GetString(); - string type = current.GetProperty(TypeJsonTag).GetString(); - string uuid = current.GetProperty(UuidJsonTag).GetString(); - var info = current.GetProperty(InfoJsonTag); - - if (type.Equals(GridJsonTag)) - { - bool error = false; - - int rows = info.GetProperty(RowsJsonTag).GetInt32(); - int columns = info.GetProperty(ColumnsJsonTag).GetInt32(); - - List rowsPercentage = new List(rows); - JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray(); - - List columnsPercentage = new List(columns); - JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray(); - - if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns) - { - error = true; - } - - while (!error && rowsPercentageEnumerator.MoveNext()) - { - int percentage = rowsPercentageEnumerator.Current.GetInt32(); - if (percentage <= 0) - { - error = true; - break; - } - - rowsPercentage.Add(percentage); - } - - while (!error && columnsPercentageEnumerator.MoveNext()) - { - int percentage = columnsPercentageEnumerator.Current.GetInt32(); - if (percentage <= 0) - { - error = true; - break; - } - - columnsPercentage.Add(percentage); - } - - int i = 0; - JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray(); - int[,] cellChildMap = new int[rows, columns]; - - if (cellChildMapRows.Count() != rows) - { - error = true; - } - - while (!error && cellChildMapRows.MoveNext()) - { - int j = 0; - JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray(); - if (cellChildMapRowElems.Count() != columns) - { - error = true; - break; - } - - while (cellChildMapRowElems.MoveNext()) - { - cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32(); - } - - i++; - } - - if (error) - { - ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name)); - _deletedCustomModels.Add(Guid.Parse(uuid).ToString().ToUpper()); - continue; - } - - _customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap)); - } - else if (type.Equals(CanvasJsonTag)) - { - int lastWorkAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32(); - int lastWorkAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32(); - - JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray(); - IList zones = new List(); - - bool error = false; - - if (lastWorkAreaWidth <= 0 || lastWorkAreaHeight <= 0) - { - error = true; - } - - while (!error && zonesEnumerator.MoveNext()) - { - int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32(); - int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32(); - int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32(); - int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32(); - - if (width <= 0 || height <= 0) - { - error = true; - break; - } - - zones.Add(new Int32Rect(x, y, width, height)); - } - - if (error) - { - ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name)); - _deletedCustomModels.Add(Guid.Parse(uuid).ToString().ToUpper()); - continue; - } - - _customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, lastWorkAreaWidth, lastWorkAreaHeight)); - } - } - - inputStream.Close(); - } - catch (Exception ex) - { - ShowExceptionMessageBox(ErrorLoadingCustomLayouts, ex); - return new ObservableCollection(); - } - + App.FancyZonesEditorIO.ParseLayouts(ref _customModels, ref _deletedCustomModels); return _customModels; } private static ObservableCollection _customModels = null; private static List _deletedCustomModels = new List(); + private static List _createdCustomLayouts = new List(); // Callbacks that the base LayoutModel makes to derived types protected abstract void PersistData(); @@ -369,83 +200,18 @@ namespace FancyZonesEditor.Models Apply(); } - private struct ActiveZoneSetWrapper - { - public string Uuid { get; set; } - - public string Type { get; set; } - } - - private struct AppliedZoneSet - { - public string DeviceId { get; set; } - - public ActiveZoneSetWrapper ActiveZoneset { get; set; } - - public bool EditorShowSpacing { get; set; } - - public int EditorSpacing { get; set; } - - public int EditorZoneCount { get; set; } - - public int EditorSensitivityRadius { get; set; } - } - public void Apply() { - ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper - { - Uuid = "{" + Guid.ToString().ToUpper() + "}", - }; + MainWindowSettingsModel settings = ((App)App.Current).MainWindowSettings; + settings.ResetAppliedModel(); + IsApplied = true; - switch (Type) - { - case LayoutType.Focus: - activeZoneSet.Type = FocusJsonTag; - break; - case LayoutType.Rows: - activeZoneSet.Type = RowsJsonTag; - break; - case LayoutType.Columns: - activeZoneSet.Type = ColumnsJsonTag; - break; - case LayoutType.Grid: - activeZoneSet.Type = GridJsonTag; - break; - case LayoutType.PriorityGrid: - activeZoneSet.Type = PriorityGridJsonTag; - break; - case LayoutType.Custom: - activeZoneSet.Type = CustomJsonTag; - break; - } + // update settings + App.Overlay.CurrentLayoutSettings.ZonesetUuid = Uuid; + App.Overlay.CurrentLayoutSettings.Type = Type; - Settings settings = ((App)Application.Current).ZoneSettings; - - AppliedZoneSet zoneSet = new AppliedZoneSet - { - DeviceId = Settings.UniqueKey, - ActiveZoneset = activeZoneSet, - EditorShowSpacing = settings.ShowSpacing, - EditorSpacing = settings.Spacing, - EditorZoneCount = settings.ZoneCount, - EditorSensitivityRadius = settings.SensitivityRadius, - }; - - JsonSerializerOptions options = new JsonSerializerOptions - { - PropertyNamingPolicy = new DashCaseNamingPolicy(), - }; - - try - { - string jsonString = JsonSerializer.Serialize(zoneSet, options); - FileSystem.File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString); - } - catch (Exception ex) - { - ShowExceptionMessageBox(ErrorApplyingLayout, ex); - } + // update temp file + App.FancyZonesEditorIO.SerializeAppliedLayouts(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutSettings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutSettings.cs new file mode 100644 index 0000000000..eea1dc3977 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutSettings.cs @@ -0,0 +1,33 @@ +// 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 FancyZonesEditor.Models; + +namespace FancyZonesEditor +{ + public class LayoutSettings + { + public static bool DefaultShowSpacing => true; + + public static int DefaultSpacing => 16; + + public static int DefaultZoneCount => 3; + + public static int DefaultSensitivityRadius => 20; + + public string DeviceId { get; set; } = string.Empty; + + public string ZonesetUuid { get; set; } = string.Empty; + + public LayoutType Type { get; set; } = LayoutType.PriorityGrid; + + public bool ShowSpacing { get; set; } = DefaultShowSpacing; + + public int Spacing { get; set; } = DefaultSpacing; + + public int ZoneCount { get; set; } = DefaultZoneCount; + + public int SensitivityRadius { get; set; } = DefaultSensitivityRadius; + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs new file mode 100644 index 0000000000..d9a51bc0f6 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutType.cs @@ -0,0 +1,17 @@ +// 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. + +namespace FancyZonesEditor.Models +{ + public enum LayoutType + { + Blank = -1, + Focus, + Columns, + Rows, + Grid, + PriorityGrid, + Custom, + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs similarity index 56% rename from src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs rename to src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs index c5229ee555..85334d286a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -6,10 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; -using System.IO.Abstractions; using System.Runtime.CompilerServices; -using System.Text.Json; using System.Windows; using FancyZonesEditor.Models; @@ -18,30 +15,16 @@ namespace FancyZonesEditor // Settings // These are the configuration settings used by the rest of the editor // Other UIs in the editor will subscribe to change events on the properties to stay up to date as these properties change - public class Settings : INotifyPropertyChanged + public class MainWindowSettingsModel : INotifyPropertyChanged { - private enum CmdArgs + private enum DeviceIdParts { - WorkAreaSize = 1, - PowerToysPID, - } - - private enum WorkAreaCmdArgElements - { - X = 0, - Y, + Name = 0, Width, Height, + VirtualDesktopId, } - private enum ParseDeviceMode - { - Prod, - Debug, - } - - private static readonly IFileSystem _fileSystem = new FileSystem(); - private static CanvasLayoutModel _blankCustomModel; private readonly CanvasLayoutModel _focusModel; private readonly GridLayoutModel _rowsModel; @@ -63,33 +46,9 @@ namespace FancyZonesEditor public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones"; public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath; - private const string ZonesSettingsFile = "\\Microsoft\\PowerToys\\FancyZones\\zones-settings.json"; - private const string ActiveZoneSetsTmpFileName = "FancyZonesActiveZoneSets.json"; - private const string AppliedZoneSetsTmpFileName = "FancyZonesAppliedZoneSets.json"; - private const string DeletedCustomZoneSetsTmpFileName = "FancyZonesDeletedCustomZoneSets.json"; - private const string LayoutTypeBlankStr = "blank"; private const string NullUuidStr = "null"; - // DeviceInfo JSON tags - private const string DeviceIdJsonTag = "device-id"; - private const string ActiveZoneSetJsonTag = "active-zoneset"; - private const string UuidJsonTag = "uuid"; - private const string TypeJsonTag = "type"; - private const string EditorShowSpacingJsonTag = "editor-show-spacing"; - private const string EditorSpacingJsonTag = "editor-spacing"; - private const string EditorZoneCountJsonTag = "editor-zone-count"; - private const string EditorSensitivityRadiusJsonTag = "editor-sensitivity-radius"; - - private const string FocusJsonTag = "focus"; - private const string ColumnsJsonTag = "columns"; - private const string RowsJsonTag = "rows"; - private const string GridJsonTag = "grid"; - private const string PriorityGridJsonTag = "priority-grid"; - private const string CustomJsonTag = "custom"; - - private const string DebugMode = "Debug"; - // hard coded data for all the "Priority Grid" configurations that are unique to "Grid" private static readonly byte[][] _priorityData = new byte[][] { @@ -124,19 +83,8 @@ namespace FancyZonesEditor } } - public Settings() + public MainWindowSettingsModel() { - string tmpDirPath = _fileSystem.Path.GetTempPath(); - - ActiveZoneSetTmpFile = tmpDirPath + ActiveZoneSetsTmpFileName; - AppliedZoneSetTmpFile = tmpDirPath + AppliedZoneSetsTmpFileName; - DeletedCustomZoneSetsTmpFile = tmpDirPath + DeletedCustomZoneSetsTmpFileName; - - var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - FancyZonesSettingsFile = localAppDataDir + ZonesSettingsFile; - - ParseCommandLineArgs(); - // Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid DefaultModels = new List(5); _focusModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Focus, LayoutType.Focus); @@ -164,7 +112,7 @@ namespace FancyZonesEditor _blankCustomModel = new CanvasLayoutModel(Properties.Resources.Custom_Layout_Create_New, LayoutType.Blank); - UpdateLayoutModels(); + UpdateTemplateLayoutModels(); } // ZoneCount - number of zones selected in the picker window @@ -172,82 +120,79 @@ namespace FancyZonesEditor { get { - return _zoneCount; + return App.Overlay.CurrentLayoutSettings.ZoneCount; } set { - if (_zoneCount != value) + if (App.Overlay.CurrentLayoutSettings.ZoneCount != value) { - _zoneCount = value; - UpdateLayoutModels(); - FirePropertyChanged(); + App.Overlay.CurrentLayoutSettings.ZoneCount = value; + UpdateTemplateLayoutModels(); + FirePropertyChanged(nameof(ZoneCount)); } } } - private int _zoneCount; - // Spacing - how much space in between zones of the grid do you want public int Spacing { get { - return _spacing; + return App.Overlay.CurrentLayoutSettings.Spacing; } set { - if (_spacing != value) + value = Math.Max(0, value); + if (App.Overlay.CurrentLayoutSettings.Spacing != value) { - _spacing = Math.Max(MaxNegativeSpacing, value); - FirePropertyChanged(); + App.Overlay.CurrentLayoutSettings.Spacing = value; + UpdateTemplateLayoutModels(); + FirePropertyChanged(nameof(Spacing)); } } } - private int _spacing; - // ShowSpacing - is the Spacing value used or ignored? public bool ShowSpacing { get { - return _showSpacing; + return App.Overlay.CurrentLayoutSettings.ShowSpacing; } set { - if (_showSpacing != value) + if (App.Overlay.CurrentLayoutSettings.ShowSpacing != value) { - _showSpacing = value; - FirePropertyChanged(); + App.Overlay.CurrentLayoutSettings.ShowSpacing = value; + UpdateTemplateLayoutModels(); + FirePropertyChanged(nameof(ShowSpacing)); } } } - private bool _showSpacing; - // SensitivityRadius - how much space inside the zone to highlight the adjacent zone too public int SensitivityRadius { get { - return _sensitivityRadius; + return App.Overlay.CurrentLayoutSettings.SensitivityRadius; } set { - if (_sensitivityRadius != value) + value = Math.Max(0, value); + if (App.Overlay.CurrentLayoutSettings.SensitivityRadius != value) { - _sensitivityRadius = Math.Max(0, value); - FirePropertyChanged(); + App.Overlay.CurrentLayoutSettings.SensitivityRadius = value; + UpdateTemplateLayoutModels(); + FirePropertyChanged(nameof(SensitivityRadius)); } } } - private int _sensitivityRadius; - // IsShiftKeyPressed - is the shift key currently being held down public bool IsShiftKeyPressed { @@ -261,7 +206,7 @@ namespace FancyZonesEditor if (_isShiftKeyPressed != value) { _isShiftKeyPressed = value; - FirePropertyChanged(); + FirePropertyChanged(nameof(IsShiftKeyPressed)); } } } @@ -281,41 +226,16 @@ namespace FancyZonesEditor if (_isCtrlKeyPressed != value) { _isCtrlKeyPressed = value; - FirePropertyChanged(); + FirePropertyChanged(nameof(IsCtrlKeyPressed)); } } } private bool _isCtrlKeyPressed; - public static Rect WorkArea { get; private set; } - - public static List UsedWorkAreas { get; private set; } - - public static string UniqueKey { get; private set; } - - public static string ActiveZoneSetUUid { get; private set; } - - public static LayoutType ActiveZoneSetLayoutType { get; private set; } - - public static string ActiveZoneSetTmpFile { get; private set; } - - public static string AppliedZoneSetTmpFile { get; private set; } - - public static string DeletedCustomZoneSetsTmpFile { get; private set; } - - public static string FancyZonesSettingsFile { get; private set; } - - public static int PowerToysPID - { - get { return _powerToysPID; } - } - - private static int _powerToysPID; - // UpdateLayoutModels // Update the five default layouts based on the new ZoneCount - private void UpdateLayoutModels() + private void UpdateTemplateLayoutModels() { // Update the "Focus" Default Layout _focusModel.Zones.Clear(); @@ -328,9 +248,13 @@ namespace FancyZonesEditor // If changing focus layout zones size and/or increment, // same change should be applied in ZoneSet.cpp (ZoneSet::CalculateFocusLayout) - Int32Rect focusZoneRect = new Int32Rect(100, 100, (int)(WorkArea.Width * 0.4), (int)(WorkArea.Height * 0.4)); - int focusRectXIncrement = (ZoneCount <= 1) ? 0 : 50; - int focusRectYIncrement = (ZoneCount <= 1) ? 0 : 50; + var workingArea = App.Overlay.WorkArea; + int topLeftCoordinate = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(100); // TODO: replace magic numbers + int width = (int)(workingArea.Width * 0.4); + int height = (int)(workingArea.Height * 0.4); + Int32Rect focusZoneRect = new Int32Rect(topLeftCoordinate, topLeftCoordinate, width, height); + int focusRectXIncrement = (ZoneCount <= 1) ? 0 : (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(50); + int focusRectYIncrement = (ZoneCount <= 1) ? 0 : (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(50); for (int i = 0; i < ZoneCount; i++) { @@ -422,128 +346,6 @@ namespace FancyZonesEditor } } - private void ParseDeviceInfoData(ParseDeviceMode mode = ParseDeviceMode.Prod) - { - try - { - string layoutType = LayoutTypeBlankStr; - ActiveZoneSetUUid = NullUuidStr; - JsonElement jsonObject = default(JsonElement); - - if (_fileSystem.File.Exists(Settings.ActiveZoneSetTmpFile)) - { - Stream inputStream = _fileSystem.File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open); - jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement; - inputStream.Close(); - UniqueKey = jsonObject.GetProperty(DeviceIdJsonTag).GetString(); - ActiveZoneSetUUid = jsonObject.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString(); - layoutType = jsonObject.GetProperty(ActiveZoneSetJsonTag).GetProperty(TypeJsonTag).GetString(); - } - - if (mode == ParseDeviceMode.Debug || ActiveZoneSetUUid == NullUuidStr || layoutType == LayoutTypeBlankStr) - { - // Default or there is no active layout on current device - ActiveZoneSetLayoutType = LayoutType.Focus; - _showSpacing = true; - _spacing = 16; - _zoneCount = 3; - _sensitivityRadius = 20; - } - else - { - switch (layoutType) - { - case FocusJsonTag: - ActiveZoneSetLayoutType = LayoutType.Focus; - break; - case ColumnsJsonTag: - ActiveZoneSetLayoutType = LayoutType.Columns; - break; - case RowsJsonTag: - ActiveZoneSetLayoutType = LayoutType.Rows; - break; - case GridJsonTag: - ActiveZoneSetLayoutType = LayoutType.Grid; - break; - case PriorityGridJsonTag: - ActiveZoneSetLayoutType = LayoutType.PriorityGrid; - break; - case CustomJsonTag: - ActiveZoneSetLayoutType = LayoutType.Custom; - break; - } - - _showSpacing = jsonObject.GetProperty(EditorShowSpacingJsonTag).GetBoolean(); - _spacing = jsonObject.GetProperty(EditorSpacingJsonTag).GetInt32(); - _zoneCount = jsonObject.GetProperty(EditorZoneCountJsonTag).GetInt32(); - _sensitivityRadius = jsonObject.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32(); - } - } - catch (Exception ex) - { - LayoutModel.ShowExceptionMessageBox(Properties.Resources.Error_Parsing_Device_Info, ex); - } - } - - private void ParseCommandLineArgs() - { - WorkArea = SystemParameters.WorkArea; - UsedWorkAreas = new List { WorkArea }; - - string[] args = Environment.GetCommandLineArgs(); - - if (args.Length == 2) - { - if (args[1].Equals(DebugMode)) - { - ParseDeviceInfoData(ParseDeviceMode.Debug); - } - else - { - MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); - ((App)Application.Current).Shutdown(); - } - } - else if (args.Length == 3) - { - UsedWorkAreas.Clear(); - foreach (var singleMonitorString in args[(int)CmdArgs.WorkAreaSize].Split('/')) - { - var parsedLocation = singleMonitorString.Split('_'); - if (parsedLocation.Length != 4) - { - MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); - ((App)Application.Current).Shutdown(); - } - - var x = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.X]); - var y = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Y]); - var width = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Width]); - var height = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Height]); - - Rect thisMonitor = new Rect(x, y, width, height); - if (UsedWorkAreas.Count == 0) - { - WorkArea = thisMonitor; - } - else - { - WorkArea = Rect.Union(WorkArea, thisMonitor); - } - - UsedWorkAreas.Add(thisMonitor); - } - - int.TryParse(args[(int)CmdArgs.PowerToysPID], out _powerToysPID); - ParseDeviceInfoData(); - } - else - { - MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); - ((App)Application.Current).Shutdown(); - } - } - public IList DefaultModels { get; } public static ObservableCollection CustomModels @@ -567,6 +369,120 @@ namespace FancyZonesEditor return model.Type != LayoutType.Custom; } + public LayoutModel UpdateSelectedLayoutModel() + { + UpdateTemplateLayoutModels(); + ResetAppliedModel(); + ResetSelectedModel(); + + LayoutModel foundModel = null; + LayoutSettings currentApplied = App.Overlay.CurrentLayoutSettings; + + // set new layout + if (currentApplied.Type == LayoutType.Custom) + { + foreach (LayoutModel model in MainWindowSettingsModel.CustomModels) + { + if ("{" + model.Guid.ToString().ToUpper() + "}" == currentApplied.ZonesetUuid.ToUpper()) + { + // found match + foundModel = model; + break; + } + } + } + else + { + foreach (LayoutModel model in DefaultModels) + { + if (model.Type == currentApplied.Type) + { + // found match + foundModel = model; + break; + } + } + } + + if (foundModel == null) + { + foundModel = DefaultModels[0]; + } + + foundModel.IsSelected = true; + foundModel.IsApplied = true; + + FirePropertyChanged(nameof(IsCustomLayoutActive)); + return foundModel; + } + + public void ResetSelectedModel() + { + foreach (LayoutModel model in CustomModels) + { + if (model.IsSelected) + { + model.IsSelected = false; + break; + } + } + + foreach (LayoutModel model in DefaultModels) + { + if (model.IsSelected) + { + model.IsSelected = false; + break; + } + } + } + + public void ResetAppliedModel() + { + foreach (LayoutModel model in CustomModels) + { + if (model.IsApplied) + { + model.IsApplied = false; + break; + } + } + + foreach (LayoutModel model in DefaultModels) + { + if (model.IsApplied) + { + model.IsApplied = false; + break; + } + } + } + + public void UpdateDesktopDependantProperties(LayoutSettings prevSettings) + { + UpdateTemplateLayoutModels(); + + if (prevSettings.ZoneCount != ZoneCount) + { + FirePropertyChanged(nameof(ZoneCount)); + } + + if (prevSettings.Spacing != Spacing) + { + FirePropertyChanged(nameof(Spacing)); + } + + if (prevSettings.ShowSpacing != ShowSpacing) + { + FirePropertyChanged(nameof(ShowSpacing)); + } + + if (prevSettings.SensitivityRadius != SensitivityRadius) + { + FirePropertyChanged(nameof(SensitivityRadius)); + } + } + // implementation of INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Monitor.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Monitor.cs new file mode 100644 index 0000000000..3dd584aae0 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Monitor.cs @@ -0,0 +1,58 @@ +// 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; +using System.Reflection; +using System.Windows; +using System.Windows.Media; +using FancyZonesEditor.Utils; + +namespace FancyZonesEditor.Models +{ + public class Monitor + { + public LayoutOverlayWindow Window { get; private set; } + + public LayoutSettings Settings { get; set; } + + public Device Device { get; set; } + + public Monitor(Rect bounds, Rect workArea, bool primary) + { + Window = new LayoutOverlayWindow(); + Settings = new LayoutSettings(); + Device = new Device(bounds, workArea, primary); + + if (App.DebugMode) + { + long milliseconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + PropertyInfo[] properties = typeof(Brushes).GetProperties(); + Window.Opacity = 0.5; + Window.Background = (Brush)properties[milliseconds % properties.Length].GetValue(null, null); + } + + Window.Left = workArea.X; + Window.Top = workArea.Y; + Window.Width = workArea.Width; + Window.Height = workArea.Height; + } + + public Monitor(string id, int dpi, Rect bounds, Rect workArea, bool primary) + : this(bounds, workArea, primary) + { + Device = new Device(id, dpi, bounds, workArea, primary); + } + + public void Scale(double scaleFactor) + { + Device.Scale(scaleFactor); + + var workArea = Device.WorkAreaRect; + Window.Left = workArea.X; + Window.Top = workArea.Y; + Window.Width = workArea.Width; + Window.Height = workArea.Height; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MonitorInfoModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MonitorInfoModel.cs new file mode 100644 index 0000000000..3a6a6f60af --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MonitorInfoModel.cs @@ -0,0 +1,86 @@ +// 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.ComponentModel; +using FancyZonesEditor.ViewModels; + +namespace FancyZonesEditor.Utils +{ + public class MonitorInfoModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public MonitorInfoModel(int index, int height, int width, int dpi, bool selected = false) + { + Index = index; + ScreenBoundsHeight = height; + ScreenBoundsWidth = width; + DPI = dpi; + Selected = selected; + } + + public int Index { get; set; } + + public int ScreenBoundsHeight { get; set; } + + public double DisplayHeight + { + get + { + return ScreenBoundsHeight * MonitorViewModel.DesktopPreviewMultiplier; + } + } + + public int ScreenBoundsWidth { get; set; } + + public double DisplayWidth + { + get + { + return ScreenBoundsWidth * MonitorViewModel.DesktopPreviewMultiplier; + } + } + + public int DPI { get; set; } + + public string Dimensions + { + get + { + if (App.DebugMode) + { + var rect = App.Overlay.Monitors[Index - 1].Device.WorkAreaRect; + return "Screen: (" + rect.X + ", " + rect.Y + "); (" + rect.Width + ", " + rect.Height + ")"; + } + else + { + return ScreenBoundsWidth + " x " + ScreenBoundsHeight; + } + } + } + + public bool Selected + { + get + { + return _selected; + } + + set + { + if (_selected == value) + { + return; + } + + _selected = value; + OnPropertyChanged(nameof(Selected)); + } + } + + private bool _selected; + + protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs new file mode 100644 index 0000000000..d61bf85499 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs @@ -0,0 +1,397 @@ +// 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; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using FancyZonesEditor.Models; + +namespace FancyZonesEditor +{ + public class Overlay + { + private MainWindow _mainWindow; + + private LayoutPreview _layoutPreview; + private UserControl _editor; + + public List Monitors { get; private set; } + + public Rect WorkArea + { + get + { + if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count) + { + return Monitors[CurrentDesktop].Device.WorkAreaRect; + } + + return default(Rect); + } + } + + public LayoutSettings CurrentLayoutSettings + { + get + { + if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count) + { + return Monitors[CurrentDesktop].Settings; + } + + return new LayoutSettings(); + } + } + + public Window CurrentLayoutWindow + { + get + { + if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count) + { + return Monitors[CurrentDesktop].Window; + } + + return default(Window); + } + } + + public List WorkAreas { get; private set; } + + public object CurrentDataContext + { + get + { + return _dataContext; + } + + set + { + _dataContext = value; + CurrentLayoutWindow.DataContext = value; + } + } + + private object _dataContext; + + public int DesktopsCount + { + get + { + return Monitors.Count; + } + } + + public int CurrentDesktop + { + get + { + return _currentDesktop; + } + + set + { + if (value != _currentDesktop) + { + if (value < 0 || value >= DesktopsCount) + { + return; + } + + var prevSettings = CurrentLayoutSettings; + _currentDesktop = value; + + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; + if (settings != null) + { + settings.ResetAppliedModel(); + settings.UpdateDesktopDependantProperties(prevSettings); + } + + Update(); + } + } + } + + private int _currentDesktop = 0; + + public bool SpanZonesAcrossMonitors + { + get + { + return _spanZonesAcrossMonitors; + } + + set + { + _spanZonesAcrossMonitors = value; + + if (_spanZonesAcrossMonitors) + { + Rect workArea = default(Rect); + Rect bounds = default(Rect); + + foreach (Monitor monitor in Monitors) + { + workArea = Rect.Union(workArea, monitor.Device.WorkAreaRect); + bounds = Rect.Union(bounds, monitor.Device.ScaledBounds); + } + + Monitors.Clear(); + Monitors.Add(new Monitor(bounds, workArea, true)); + } + } + } + + private bool _spanZonesAcrossMonitors; + + public bool MultiMonitorMode + { + get + { + return DesktopsCount > 1 && !SpanZonesAcrossMonitors; + } + } + + public Overlay() + { + WorkAreas = new List(); + Monitors = new List(); + + var screens = System.Windows.Forms.Screen.AllScreens; + foreach (System.Windows.Forms.Screen screen in screens) + { + Rect bounds = new Rect(screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height); + Rect workArea = new Rect(screen.WorkingArea.X, screen.WorkingArea.Y, screen.WorkingArea.Width, screen.WorkingArea.Height); + Add(bounds, workArea, screen.Primary); + } + } + + public void Show() + { + _layoutPreview = new LayoutPreview + { + IsActualSize = true, + Opacity = 0.5, + }; + + ShowLayout(); + OpenMainWindow(); + } + + public void ShowLayout() + { + MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings; + CurrentDataContext = settings.UpdateSelectedLayoutModel(); + + var window = CurrentLayoutWindow; + window.Content = _layoutPreview; + window.DataContext = CurrentDataContext; + + if (_layoutPreview != null) + { + _layoutPreview.UpdatePreview(); + } + + for (int i = 0; i < DesktopsCount; i++) + { + Monitors[i].Window.Show(); + } + } + + public void OpenEditor(LayoutModel model) + { + _layoutPreview = null; + if (CurrentDataContext is GridLayoutModel) + { + _editor = new GridEditor(); + } + else if (CurrentDataContext is CanvasLayoutModel) + { + _editor = new CanvasEditor(); + } + + CurrentLayoutWindow.Content = _editor; + + EditorWindow window; + bool isGrid = false; + if (model is GridLayoutModel) + { + window = new GridEditorWindow(); + isGrid = true; + } + else + { + window = new CanvasEditorWindow(); + } + + window.Owner = Monitors[App.Overlay.CurrentDesktop].Window; + window.DataContext = model; + window.Show(); + + if (isGrid) + { + (window as GridEditorWindow).NameTextBox().Focus(); + } + + window.LeftWindowCommands = null; + window.RightWindowCommands = null; + } + + public void CloseEditor() + { + _editor = null; + _layoutPreview = new LayoutPreview + { + IsActualSize = true, + Opacity = 0.5, + }; + + CurrentLayoutWindow.Content = _layoutPreview; + + OpenMainWindow(); + } + + public void CloseLayoutWindow() + { + for (int i = 0; i < DesktopsCount; i++) + { + Monitors[i].Window.Close(); + } + } + + public double ScaleCoordinateWithCurrentMonitorDpi(double coordinate) + { + if (Monitors.Count == 0) + { + return coordinate; + } + + double minimalDpi = Monitors[0].Device.Dpi; + foreach (Monitor monitor in Monitors) + { + if (minimalDpi > monitor.Device.Dpi) + { + minimalDpi = monitor.Device.Dpi; + } + } + + if (minimalDpi == 0 || Monitors[CurrentDesktop].Device.Dpi == 0) + { + return coordinate; + } + + double scaleFactor = minimalDpi / Monitors[CurrentDesktop].Device.Dpi; + return Math.Round(coordinate * scaleFactor); + } + + private void Update() + { + CloseLayout(); + + if (_mainWindow != null) + { + _mainWindow.Update(); + } + + ShowLayout(); + } + + private void CloseLayout() + { + var window = CurrentLayoutWindow; + window.Content = null; + window.DataContext = null; + } + + private void OpenMainWindow() + { + if (_mainWindow == null) + { + _mainWindow = new MainWindow(SpanZonesAcrossMonitors, WorkArea); + } + + // reset main window owner to keep it on the top + _mainWindow.Owner = CurrentLayoutWindow; + _mainWindow.ShowActivated = true; + _mainWindow.Topmost = true; + _mainWindow.Show(); + _mainWindow.LeftWindowCommands = null; + _mainWindow.RightWindowCommands = null; + + // window is set to topmost to make sure it shows on top of PowerToys settings page + // we can reset topmost flag now + _mainWindow.Topmost = false; + } + + private void Add(Rect bounds, Rect workArea, bool primary) + { + var monitor = new Monitor(bounds, workArea, primary); + + bool inserted = false; + var workAreaRect = workArea; + for (int i = 0; i < Monitors.Count && !inserted; i++) + { + var rect = Monitors[i].Device.WorkAreaRect; + if (workAreaRect.Left < rect.Left && (workAreaRect.Top <= rect.Top || workAreaRect.Top == 0)) + { + Monitors.Insert(i, monitor); + inserted = true; + } + else if (workAreaRect.Left == rect.Left && workAreaRect.Top < rect.Top) + { + Monitors.Insert(i, monitor); + inserted = true; + } + } + + if (!inserted) + { + Monitors.Add(monitor); + } + } + + public Int32Rect[] GetZoneRects() + { + if (_editor != null) + { + if (_editor is GridEditor gridEditor) + { + return ZoneRectsFromPanel(gridEditor.PreviewPanel); + } + else + { + // CanvasEditor + return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview); + } + } + else + { + // One of the predefined zones (neither grid or canvas editor used). + return _layoutPreview.GetZoneRects(); + } + } + + private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel) + { + // TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info + int count = previewPanel.Children.Count; + Int32Rect[] zones = new Int32Rect[count]; + + for (int i = 0; i < count; i++) + { + FrameworkElement child = (FrameworkElement)previewPanel.Children[i]; + Point topLeft = child.TransformToAncestor(previewPanel).Transform(default); + + zones[i].X = (int)topLeft.X; + zones[i].Y = (int)topLeft.Y; + zones[i].Width = (int)child.ActualWidth; + zones[i].Height = (int)child.ActualHeight; + } + + return zones; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs index 8e8f3d2621..301b529106 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs @@ -105,6 +105,15 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Close. + /// + public static string Close { + get { + return ResourceManager.GetString("Close", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error logged to . /// @@ -186,6 +195,24 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Error applying layout. + /// + public static string Error_Applying_Layout { + get { + return ResourceManager.GetString("Error_Applying_Layout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to FancyZones Editor Exception Handler. + /// + public static string Error_Exception_Message_Box_Title { + get { + return ResourceManager.GetString("Error_Exception_Message_Box_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to FancyZones Editor arguments are invalid.. /// @@ -195,6 +222,24 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Layout '{0}' has malformed data. + /// + public static string Error_Layout_Malformed_Data { + get { + return ResourceManager.GetString("Error_Layout_Malformed_Data", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error loading custom layouts. + /// + public static string Error_Loading_Custom_Layouts { + get { + return ResourceManager.GetString("Error_Loading_Custom_Layouts", resourceCulture); + } + } + /// /// Looks up a localized string similar to FancyZones Editor Error. /// @@ -204,6 +249,15 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Match not found ({0}). + /// + public static string Error_Monitor_Match_Not_Found { + get { + return ResourceManager.GetString("Error_Monitor_Match_Not_Found", resourceCulture); + } + } + /// /// Looks up a localized string similar to FancyZones Editor should not be run as standalone application.. /// @@ -222,6 +276,33 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Error persisting custom layout. + /// + public static string Error_Persisting_Custom_Layout { + get { + return ResourceManager.GetString("Error_Persisting_Custom_Layout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please report the bug to . + /// + public static string Error_Report { + get { + return ResourceManager.GetString("Error_Report", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error serializing deleted layouts. + /// + public static string Error_Serializing_Deleted_Layouts { + get { + return ResourceManager.GetString("Error_Serializing_Deleted_Layouts", resourceCulture); + } + } + /// /// Looks up a localized string similar to FancyZones Editor. /// diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx index c95348bbb1..52be380f27 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx @@ -226,4 +226,31 @@ Templates tab selected, press ctrl + tab to switch to Custom + + Close + + + Error applying layout + + + Layout '{0}' has malformed data + + + Error loading custom layouts + + + Error persisting custom layout + + + Error serializing deleted layouts + + + FancyZones Editor Exception Handler + + + Please report the bug to + + + Match not found ({0}) + \ No newline at end of file diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventArgs`1.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventArgs`1.cs new file mode 100644 index 0000000000..eeea75476e --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventArgs`1.cs @@ -0,0 +1,18 @@ +// 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; + +namespace FancyZonesEditor.Utils +{ + public class EventArgs : EventArgs + { + public EventArgs(T value) + { + Value = value; + } + + public T Value { get; private set; } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventRaiser.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventRaiser.cs new file mode 100644 index 0000000000..6cdce5f35d --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/EventRaiser.cs @@ -0,0 +1,32 @@ +// 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; + +namespace FancyZonesEditor.Utils +{ + public static class EventRaiser + { + public static void Raise(this EventHandler handler, object sender) + { + handler?.Invoke(sender, EventArgs.Empty); + } + + public static void Raise(this EventHandler> handler, object sender, T value) + { + handler?.Invoke(sender, new EventArgs(value)); + } + + public static void Raise(this EventHandler handler, object sender, T value) + where T : EventArgs + { + handler?.Invoke(sender, value); + } + + public static void Raise(this EventHandler> handler, object sender, EventArgs value) + { + handler?.Invoke(sender, value); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs new file mode 100644 index 0000000000..a34886565a --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/FancyZonesEditorIO.cs @@ -0,0 +1,678 @@ +// 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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.IO.Abstractions; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Windows; +using FancyZonesEditor.Models; + +namespace FancyZonesEditor.Utils +{ + public class FancyZonesEditorIO + { + // Non-localizable strings: JSON tags + private const string AppliedZonesetsJsonTag = "applied-zonesets"; + private const string DeviceIdJsonTag = "device-id"; + private const string ActiveZoneSetJsonTag = "active-zoneset"; + private const string UuidJsonTag = "uuid"; + private const string TypeJsonTag = "type"; + private const string EditorShowSpacingJsonTag = "editor-show-spacing"; + private const string EditorSpacingJsonTag = "editor-spacing"; + private const string EditorZoneCountJsonTag = "editor-zone-count"; + private const string EditorSensitivityRadiusJsonTag = "editor-sensitivity-radius"; + + private const string FocusJsonTag = "focus"; + private const string ColumnsJsonTag = "columns"; + private const string RowsJsonTag = "rows"; + private const string GridJsonTag = "grid"; + private const string PriorityGridJsonTag = "priority-grid"; + private const string CustomJsonTag = "custom"; + + private const string NameJsonTag = "name"; + private const string CustomZoneSetsJsonTag = "custom-zone-sets"; + private const string InfoJsonTag = "info"; + private const string RowsPercentageJsonTag = "rows-percentage"; + private const string ColumnsPercentageJsonTag = "columns-percentage"; + private const string CellChildMapJsonTag = "cell-child-map"; + private const string ZonesJsonTag = "zones"; + private const string CanvasJsonTag = "canvas"; + private const string RefWidthJsonTag = "ref-width"; + private const string RefHeightJsonTag = "ref-height"; + private const string XJsonTag = "X"; + private const string YJsonTag = "Y"; + private const string WidthJsonTag = "width"; + private const string HeightJsonTag = "height"; + + // Non-localizable strings: Files + private const string ZonesSettingsFile = "\\Microsoft\\PowerToys\\FancyZones\\zones-settings.json"; + private const string ActiveZoneSetsTmpFileName = "FancyZonesActiveZoneSets.json"; + private const string AppliedZoneSetsTmpFileName = "FancyZonesAppliedZoneSets.json"; + private const string DeletedCustomZoneSetsTmpFileName = "FancyZonesDeletedCustomZoneSets.json"; + + private readonly IFileSystem _fileSystem = new FileSystem(); + + private JsonSerializerOptions _options = new JsonSerializerOptions + { + PropertyNamingPolicy = new DashCaseNamingPolicy(), + }; + + public string ActiveZoneSetTmpFile { get; private set; } + + public string AppliedZoneSetTmpFile { get; private set; } + + public string DeletedCustomZoneSetsTmpFile { get; private set; } + + public string FancyZonesSettingsFile { get; private set; } + + private enum CmdArgs + { + PowerToysPID = 0, + SpanZones, + TargetMonitorId, + MonitorsCount, + MonitorId, + DPI, + MonitorLeft, + MonitorTop, + } + + private struct NativeMonitorData + { + public string Id { get; set; } + + public int Dpi { get; set; } + + public int X { get; set; } + + public int Y { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.Append("ID: "); + sb.AppendLine(Id); + sb.Append("DPI: "); + sb.AppendLine(Dpi.ToString()); + + sb.Append("X: "); + sb.AppendLine(X.ToString()); + sb.Append("Y: "); + sb.AppendLine(Y.ToString()); + + return sb.ToString(); + } + } + + private struct ActiveZoneSetWrapper + { + public string Uuid { get; set; } + + public string Type { get; set; } + } + + private struct AppliedZoneSet + { + public string DeviceId { get; set; } + + public ActiveZoneSetWrapper ActiveZoneset { get; set; } + + public bool EditorShowSpacing { get; set; } + + public int EditorSpacing { get; set; } + + public int EditorZoneCount { get; set; } + + public int EditorSensitivityRadius { get; set; } + } + + private struct AppliedZonesetsToDesktops + { + public List AppliedZonesets { get; set; } + } + + private struct DeletedCustomZoneSetsWrapper + { + public List DeletedCustomZoneSets { get; set; } + } + + private struct CreatedCustomZoneSetsWrapper + { + public List CreatedCustomZoneSets { get; set; } + } + + public FancyZonesEditorIO() + { + string tmpDirPath = _fileSystem.Path.GetTempPath(); + + ActiveZoneSetTmpFile = tmpDirPath + ActiveZoneSetsTmpFileName; + AppliedZoneSetTmpFile = tmpDirPath + AppliedZoneSetsTmpFileName; + DeletedCustomZoneSetsTmpFile = tmpDirPath + DeletedCustomZoneSetsTmpFileName; + + var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + FancyZonesSettingsFile = localAppDataDir + ZonesSettingsFile; + } + + // All strings in this function shouldn't be localized. + public static void ParseCommandLineArguments() + { + string[] args = Environment.GetCommandLineArgs(); + + if (args.Length < 2 && !App.DebugMode) + { + MessageBox.Show(Properties.Resources.Error_Not_Standalone_App, Properties.Resources.Error_Message_Box_Title); + ((App)Application.Current).Shutdown(); + } + + try + { + /* + * Divider: / + * Parts: + * (1) Process id + * (2) Span zones across monitors + * (3) Monitor id where the Editor should be opened + * (4) Monitors count + * + * Data for each monitor: + * (5) Monitor id + * (6) DPI + * (7) monitor left + * (8) monitor top + * ... + */ + var argsParts = args[1].Split('/'); + + // Process ID + App.PowerToysPID = int.Parse(argsParts[(int)CmdArgs.PowerToysPID]); + + // Span zones across monitors + App.Overlay.SpanZonesAcrossMonitors = int.Parse(argsParts[(int)CmdArgs.SpanZones]) == 1; + + if (!App.Overlay.SpanZonesAcrossMonitors) + { + // Target monitor id + string targetMonitorName = argsParts[(int)CmdArgs.TargetMonitorId]; + + // Monitors count + int count = int.Parse(argsParts[(int)CmdArgs.MonitorsCount]); + if (count != App.Overlay.DesktopsCount) + { + MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); + ((App)Application.Current).Shutdown(); + } + + double primaryMonitorDPI = 96f; + double minimalUsedMonitorDPI = double.MaxValue; + + // Parse the native monitor data + List nativeMonitorData = new List(); + const int monitorArgsCount = 4; + for (int i = 0; i < count; i++) + { + var nativeData = default(NativeMonitorData); + nativeData.Id = argsParts[(int)CmdArgs.MonitorId + (i * monitorArgsCount)]; + nativeData.Dpi = int.Parse(argsParts[(int)CmdArgs.DPI + (i * monitorArgsCount)]); + nativeData.X = int.Parse(argsParts[(int)CmdArgs.MonitorLeft + (i * monitorArgsCount)]); + nativeData.Y = int.Parse(argsParts[(int)CmdArgs.MonitorTop + (i * monitorArgsCount)]); + nativeMonitorData.Add(nativeData); + + if (nativeData.X == 0 && nativeData.Y == 0) + { + primaryMonitorDPI = nativeData.Dpi; + } + + if (minimalUsedMonitorDPI > nativeData.Dpi) + { + minimalUsedMonitorDPI = nativeData.Dpi; + } + } + + var monitors = App.Overlay.Monitors; + double identifyScaleFactor = minimalUsedMonitorDPI / primaryMonitorDPI; + double scaleFactor = 96f / primaryMonitorDPI; + + // Update monitors data + foreach (Monitor monitor in monitors) + { + bool matchFound = false; + monitor.Scale(scaleFactor); + + double scaledBoundX = (int)(monitor.Device.UnscaledBounds.X * identifyScaleFactor); + double scaledBoundY = (int)(monitor.Device.UnscaledBounds.Y * identifyScaleFactor); + + foreach (NativeMonitorData nativeData in nativeMonitorData) + { + // Can't do an exact match since the rounding algorithm used by the framework is different from ours + if (scaledBoundX >= (nativeData.X - 1) && scaledBoundX <= (nativeData.X + 1) && + scaledBoundY >= (nativeData.Y - 1) && scaledBoundY <= (nativeData.Y + 1)) + { + monitor.Device.Id = nativeData.Id; + monitor.Device.Dpi = nativeData.Dpi; + matchFound = true; + break; + } + } + + if (matchFound == false) + { + MessageBox.Show(string.Format(Properties.Resources.Error_Monitor_Match_Not_Found, monitor.Device.UnscaledBounds.ToString())); + } + } + + // Set active desktop + for (int i = 0; i < monitors.Count; i++) + { + var monitor = monitors[i]; + if (monitor.Device.Id == targetMonitorName) + { + App.Overlay.CurrentDesktop = i; + break; + } + } + } + } + catch (Exception) + { + MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title); + ((App)Application.Current).Shutdown(); + } + } + + public void ParseDeviceInfoData() + { + try + { + JsonElement jsonObject = default(JsonElement); + + if (_fileSystem.File.Exists(ActiveZoneSetTmpFile)) + { + Stream inputStream = _fileSystem.File.Open(ActiveZoneSetTmpFile, FileMode.Open); + jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement; + inputStream.Close(); + + JsonElement json = jsonObject.GetProperty(AppliedZonesetsJsonTag); + + int layoutId = 0; + for (int i = 0; i < json.GetArrayLength() && layoutId < App.Overlay.DesktopsCount; i++) + { + var zonesetData = json[i]; + + string deviceId = zonesetData.GetProperty(DeviceIdJsonTag).GetString(); + + string currentLayoutType = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(TypeJsonTag).GetString(); + LayoutType type = JsonTagToLayoutType(currentLayoutType); + + if (!App.Overlay.SpanZonesAcrossMonitors) + { + var monitors = App.Overlay.Monitors; + for (int monitorIndex = 0; monitorIndex < monitors.Count; monitorIndex++) + { + if (monitors[monitorIndex].Device.Id == deviceId) + { + monitors[monitorIndex].Settings = new LayoutSettings + { + DeviceId = deviceId, + ZonesetUuid = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString(), + ShowSpacing = zonesetData.GetProperty(EditorShowSpacingJsonTag).GetBoolean(), + Spacing = zonesetData.GetProperty(EditorSpacingJsonTag).GetInt32(), + Type = type, + ZoneCount = zonesetData.GetProperty(EditorZoneCountJsonTag).GetInt32(), + SensitivityRadius = zonesetData.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32(), + }; + + break; + } + } + } + else + { + bool isLayoutMultiMonitor = deviceId.StartsWith("FancyZones#MultiMonitorDevice"); + if (isLayoutMultiMonitor) + { + // one zoneset for all desktops + App.Overlay.Monitors[App.Overlay.CurrentDesktop].Settings = new LayoutSettings + { + DeviceId = deviceId, + ZonesetUuid = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString(), + ShowSpacing = zonesetData.GetProperty(EditorShowSpacingJsonTag).GetBoolean(), + Spacing = zonesetData.GetProperty(EditorSpacingJsonTag).GetInt32(), + Type = type, + ZoneCount = zonesetData.GetProperty(EditorZoneCountJsonTag).GetInt32(), + SensitivityRadius = zonesetData.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32(), + }; + + break; + } + } + } + } + } + catch (Exception ex) + { + App.ShowExceptionMessageBox(Properties.Resources.Error_Parsing_Device_Info, ex); + } + } + + public void ParseLayouts(ref ObservableCollection custom, ref List deleted) + { + try + { + Stream inputStream = _fileSystem.File.Open(FancyZonesSettingsFile, FileMode.Open); + JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default); + JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray(); + + while (customZoneSetsEnumerator.MoveNext()) + { + var current = customZoneSetsEnumerator.Current; + string name = current.GetProperty(NameJsonTag).GetString(); + string type = current.GetProperty(TypeJsonTag).GetString(); + string uuid = current.GetProperty(UuidJsonTag).GetString(); + var info = current.GetProperty(InfoJsonTag); + + if (type.Equals(GridJsonTag)) + { + bool error = false; + + int rows = info.GetProperty(RowsJsonTag).GetInt32(); + int columns = info.GetProperty(ColumnsJsonTag).GetInt32(); + + List rowsPercentage = new List(rows); + JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray(); + + List columnsPercentage = new List(columns); + JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray(); + + if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns) + { + error = true; + } + + while (!error && rowsPercentageEnumerator.MoveNext()) + { + int percentage = rowsPercentageEnumerator.Current.GetInt32(); + if (percentage <= 0) + { + error = true; + break; + } + + rowsPercentage.Add(percentage); + } + + while (!error && columnsPercentageEnumerator.MoveNext()) + { + int percentage = columnsPercentageEnumerator.Current.GetInt32(); + if (percentage <= 0) + { + error = true; + break; + } + + columnsPercentage.Add(percentage); + } + + int i = 0; + JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray(); + int[,] cellChildMap = new int[rows, columns]; + + if (cellChildMapRows.Count() != rows) + { + error = true; + } + + while (!error && cellChildMapRows.MoveNext()) + { + int j = 0; + JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray(); + if (cellChildMapRowElems.Count() != columns) + { + error = true; + break; + } + + while (cellChildMapRowElems.MoveNext()) + { + cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32(); + } + + i++; + } + + if (error) + { + App.ShowExceptionMessageBox(string.Format(Properties.Resources.Error_Layout_Malformed_Data, name)); + deleted.Add(Guid.Parse(uuid).ToString().ToUpper()); + continue; + } + + custom.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap)); + } + else if (type.Equals(CanvasJsonTag)) + { + int workAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32(); + int workAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32(); + + JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray(); + IList zones = new List(); + + bool error = false; + + if (workAreaWidth <= 0 || workAreaHeight <= 0) + { + error = true; + } + + while (!error && zonesEnumerator.MoveNext()) + { + int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32(); + int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32(); + int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32(); + int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32(); + + if (width <= 0 || height <= 0) + { + error = true; + break; + } + + zones.Add(new Int32Rect(x, y, width, height)); + } + + if (error) + { + App.ShowExceptionMessageBox(string.Format(Properties.Resources.Error_Layout_Malformed_Data, name)); + deleted.Add(Guid.Parse(uuid).ToString().ToUpper()); + continue; + } + + custom.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, workAreaWidth, workAreaHeight)); + } + } + + inputStream.Close(); + } + catch (Exception ex) + { + App.ShowExceptionMessageBox(Properties.Resources.Error_Loading_Custom_Layouts, ex); + } + } + + public void SerializeAppliedLayouts() + { + AppliedZonesetsToDesktops applied = new AppliedZonesetsToDesktops { }; + applied.AppliedZonesets = new List(); + + foreach (var monitor in App.Overlay.Monitors) + { + LayoutSettings zoneset = monitor.Settings; + if (zoneset.ZonesetUuid.Length == 0) + { + continue; + } + + ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper + { + Uuid = zoneset.ZonesetUuid, + }; + + activeZoneSet.Type = LayoutTypeToJsonTag(zoneset.Type); + + applied.AppliedZonesets.Add(new AppliedZoneSet + { + DeviceId = zoneset.DeviceId, + ActiveZoneset = activeZoneSet, + EditorShowSpacing = zoneset.ShowSpacing, + EditorSpacing = zoneset.Spacing, + EditorZoneCount = zoneset.ZoneCount, + EditorSensitivityRadius = zoneset.SensitivityRadius, + }); + } + + try + { + string jsonString = JsonSerializer.Serialize(applied, _options); + _fileSystem.File.WriteAllText(ActiveZoneSetTmpFile, jsonString); + } + catch (Exception ex) + { + App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex); + } + } + + public void SerializeDeletedCustomZoneSets(List models) + { + DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper + { + DeletedCustomZoneSets = models, + }; + + try + { + string jsonString = JsonSerializer.Serialize(deletedLayouts, _options); + _fileSystem.File.WriteAllText(DeletedCustomZoneSetsTmpFile, jsonString); + } + catch (Exception ex) + { + App.ShowExceptionMessageBox(Properties.Resources.Error_Serializing_Deleted_Layouts, ex); + } + } + + public void SerializeCreatedCustomZonesets(List models) + { + CreatedCustomZoneSetsWrapper layouts = new CreatedCustomZoneSetsWrapper + { + CreatedCustomZoneSets = models, + }; + + try + { + string jsonString = JsonSerializer.Serialize(layouts, _options); + _fileSystem.File.WriteAllText(AppliedZoneSetTmpFile, jsonString); + } + catch (Exception ex) + { + App.ShowExceptionMessageBox(Properties.Resources.Error_Persisting_Custom_Layout, ex); + } + } + + private LayoutType JsonTagToLayoutType(string tag) + { + LayoutType type = LayoutType.Blank; + switch (tag) + { + case FocusJsonTag: + type = LayoutType.Focus; + break; + case ColumnsJsonTag: + type = LayoutType.Columns; + break; + case RowsJsonTag: + type = LayoutType.Rows; + break; + case GridJsonTag: + type = LayoutType.Grid; + break; + case PriorityGridJsonTag: + type = LayoutType.PriorityGrid; + break; + case CustomJsonTag: + type = LayoutType.Custom; + break; + } + + return type; + } + + private string LayoutTypeToJsonTag(LayoutType type) + { + switch (type) + { + case LayoutType.Focus: + return FocusJsonTag; + case LayoutType.Rows: + return RowsJsonTag; + case LayoutType.Columns: + return ColumnsJsonTag; + case LayoutType.Grid: + return GridJsonTag; + case LayoutType.PriorityGrid: + return PriorityGridJsonTag; + case LayoutType.Custom: + return CustomJsonTag; + } + + return string.Empty; + } + + private static string ParsingCmdArgsErrorReport(string args, int count, string targetMonitorName, List monitorData, List monitors) + { + var sb = new StringBuilder(); + + sb.AppendLine(); + sb.AppendLine("```"); + sb.AppendLine(" ## Command-line arguments:"); + sb.AppendLine(); + sb.AppendLine(args); + + sb.AppendLine(); + sb.AppendLine("```"); + sb.AppendLine(" ## Parsed command-line arguments:"); + sb.AppendLine(); + + sb.Append("Span zones across monitors: "); + sb.AppendLine(App.Overlay.SpanZonesAcrossMonitors.ToString()); + sb.Append("Monitors count: "); + sb.AppendLine(count.ToString()); + sb.Append("Target monitor: "); + sb.AppendLine(targetMonitorName); + + sb.AppendLine(); + sb.AppendLine(" # Per monitor data:"); + sb.AppendLine(); + foreach (NativeMonitorData data in monitorData) + { + sb.AppendLine(data.ToString()); + } + + sb.AppendLine(); + sb.AppendLine("```"); + sb.AppendLine(" ## Monitors discovered:"); + sb.AppendLine(); + + foreach (Monitor m in monitors) + { + sb.AppendLine(m.Device.ToString()); + } + + return sb.ToString(); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/MonitorChangedEventArgs.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/MonitorChangedEventArgs.cs new file mode 100644 index 0000000000..2daa5a3692 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/MonitorChangedEventArgs.cs @@ -0,0 +1,18 @@ +// 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; + +namespace FancyZonesEditor.Utils +{ + public class MonitorChangedEventArgs : EventArgs + { + public int LastMonitor { get; } + + public MonitorChangedEventArgs(int lastMonitor) + { + LastMonitor = lastMonitor; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand.cs new file mode 100644 index 0000000000..8086689b5e --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand.cs @@ -0,0 +1,67 @@ +// 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; +using System.Windows.Input; + +namespace FancyZonesEditor.Utils +{ + public class RelayCommand : ICommand + { + private readonly Predicate _canExecute; + private readonly Action _execute; + + public RelayCommand(Action execute) + : this(execute, null) + { + _execute = execute; + } + + public RelayCommand(Action execute, Predicate canExecute) + { + if (execute == null) + { + throw new ArgumentNullException("execute"); + } + + _execute = execute; + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute(parameter); + } + + // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their + // associated views should be enabled whenever a command is invoked + public event EventHandler CanExecuteChanged + { + add + { + CommandManager.RequerySuggested += value; + CanExecuteChangedInternal += value; + } + + remove + { + CommandManager.RequerySuggested -= value; + CanExecuteChangedInternal -= value; + } + } + + private event EventHandler CanExecuteChangedInternal; + + public void RaiseCanExecuteChanged() + { + CanExecuteChangedInternal.Invoke(this, null); + /*CanExecuteChangedInternal.Raise(this);*/ + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand`1.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand`1.cs new file mode 100644 index 0000000000..21c9ed7d17 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Utils/RelayCommand`1.cs @@ -0,0 +1,48 @@ +// 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; +using System.Windows.Input; + +namespace FancyZonesEditor.Utils +{ + public class RelayCommand : ICommand + { + private readonly Predicate _canExecute; + private readonly Action _execute; + + public RelayCommand(Action execute) + : this(execute, null) + { + _execute = execute; + } + + public RelayCommand(Action execute, Predicate canExecute) + { + if (execute == null) + { + throw new ArgumentNullException("execute"); + } + + _execute = execute; + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) + { + return _canExecute == null || _canExecute((T)parameter); + } + + public void Execute(object parameter) + { + _execute((T)parameter); + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/ViewModels/MonitorViewModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/ViewModels/MonitorViewModel.cs new file mode 100644 index 0000000000..080b6b509f --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/ViewModels/MonitorViewModel.cs @@ -0,0 +1,81 @@ +// 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.ObjectModel; +using System.ComponentModel; +using System.Windows; +using FancyZonesEditor.Utils; + +namespace FancyZonesEditor.ViewModels +{ + public class MonitorViewModel : INotifyPropertyChanged + { + private const int MaxPreviewDisplaySize = 160; + private const int MinPreviewDisplaySize = 98; + + public event PropertyChangedEventHandler PropertyChanged; + + public delegate void MonitorChangedEventHandler(MonitorChangedEventArgs args); + + public ObservableCollection MonitorInfoForViewModel { get; set; } + + public static double DesktopPreviewMultiplier { get; private set; } + + public Visibility DesktopsPanelVisibility + { + get + { + return App.Overlay.MultiMonitorMode ? Visibility.Visible : Visibility.Collapsed; + } + } + + public RelayCommand AddCommand { get; set; } + + public RelayCommand DeleteCommand { get; set; } + + public RelayCommand SelectCommand { get; set; } + + public MonitorViewModel() + { + SelectCommand = new RelayCommand(SelectCommandExecute, SelectCommandCanExecute); + + MonitorInfoForViewModel = new ObservableCollection(); + double maxDimension = 0, minDimension = double.MaxValue; + + int i = 1; + foreach (var monitor in App.Overlay.Monitors) + { + Device device = monitor.Device; + var bounds = device.ScaledBounds; + maxDimension = System.Math.Max(System.Math.Max(maxDimension, bounds.Height), bounds.Width); + minDimension = System.Math.Min(System.Math.Min(minDimension, bounds.Height), bounds.Width); + + MonitorInfoForViewModel.Add(new MonitorInfoModel(i, (int)bounds.Height, (int)bounds.Width, device.Dpi, App.Overlay.CurrentDesktop == i - 1)); + i++; + } + + double maxMultiplier = MaxPreviewDisplaySize / maxDimension; + double minMultiplier = MinPreviewDisplaySize / minDimension; + DesktopPreviewMultiplier = (minMultiplier + maxMultiplier) / 2; + } + + private void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private bool SelectCommandCanExecute(MonitorInfoModel monitorInfo) + { + return true; + } + + private void SelectCommandExecute(MonitorInfoModel monitorInfo) + { + MonitorInfoForViewModel[App.Overlay.CurrentDesktop].Selected = false; + MonitorInfoForViewModel[monitorInfo.Index - 1].Selected = true; + + App.Overlay.CurrentDesktop = monitorInfo.Index - 1; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/app.manifest b/src/modules/fancyzones/editor/FancyZonesEditor/app.manifest new file mode 100644 index 0000000000..a52bf87d3a --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/app.manifest @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + System + + + + + + + diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 1b6632c232..239227d582 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -592,133 +592,99 @@ void FancyZones::ToggleEditor() noexcept m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); } - HMONITOR monitor{}; - HWND foregroundWindow{}; - - const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen; - POINT currentCursorPos{}; - if (use_cursorpos_editor_startupscreen) - { - GetCursorPos(¤tCursorPos); - monitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY); - } - else - { - foregroundWindow = GetForegroundWindow(); - monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTOPRIMARY); - } - - if (!monitor) - { - return; - } - - winrt::com_ptr zoneWindow; - std::shared_lock readLock(m_lock); - if (m_settings->GetSettings()->spanZonesAcrossMonitors) + HMONITOR targetMonitor{}; + + const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen; + if (use_cursorpos_editor_startupscreen) { - zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, NULL); + POINT currentCursorPos{}; + GetCursorPos(¤tCursorPos); + targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY); } else { - zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); + targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY); } - if (!zoneWindow) + if (!targetMonitor) { return; } - std::wstring editorLocation; + /* + * Divider: / + * Parts: + * (1) Process id + * (2) Span zones across monitors + * (3) Monitor id where the Editor should be opened + * (4) Monitors count + * + * Data for each monitor: + * (5) Monitor id + * (6) DPI + * (7) monitor left + * (8) monitor top + * ... + */ + std::wstring params; + const std::wstring divider = L"/"; + params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */ - if (m_settings->GetSettings()->spanZonesAcrossMonitors) + const bool spanZonesAcrossMonitors = m_settings->GetSettings()->spanZonesAcrossMonitors; + params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */ + + std::vector> allMonitors; + allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>(); + + bool showDpiWarning = false; + int prevDpiX = -1, prevDpiY = -1; + std::wstring monitorsData; + for (auto& monitor : allMonitors) { - std::vector> allMonitors; - - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { - allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); - } }) - .wait(); - - UINT currentDpi = 0; - for (const auto& monitor : allMonitors) + auto monitorId = FancyZonesUtils::GenerateMonitorId(monitor.second, monitor.first, m_currentDesktopId); + if (monitor.first == targetMonitor) { + params += *monitorId + divider; /* Monitor id where the Editor should be opened */ + } + + if (monitorId.has_value()) + { + monitorsData += std::move(*monitorId) + divider; /* Monitor id */ + UINT dpiX = 0; UINT dpiY = 0; if (GetDpiForMonitor(monitor.first, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) { - if (currentDpi == 0) + monitorsData += std::to_wstring(dpiX) + divider; /* DPI */ + if (spanZonesAcrossMonitors && prevDpiX != -1 && (prevDpiX != dpiX || prevDpiY != dpiY)) { - currentDpi = dpiX; - continue; + showDpiWarning = true; } - 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); - break; - } - } - } - for (auto& [monitor, workArea] : allMonitors) - { - const auto x = workArea.left; - const auto y = workArea.top; - const auto width = workArea.right - workArea.left; - const auto height = workArea.bottom - workArea.top; - std::wstring editorLocationPart = - std::to_wstring(x) + L"_" + - std::to_wstring(y) + L"_" + - std::to_wstring(width) + L"_" + - std::to_wstring(height); + prevDpiX = dpiX; + prevDpiY = dpiY; + } - if (editorLocation.empty()) - { - editorLocation = std::move(editorLocationPart); - } - else - { - editorLocation += L'/'; - editorLocation += editorLocationPart; - } + monitorsData += std::to_wstring(monitor.second.rcMonitor.left) + divider; + monitorsData += std::to_wstring(monitor.second.rcMonitor.top) + divider; } } - else + + params += std::to_wstring(allMonitors.size()) + divider; /* Monitors count */ + params += monitorsData; + + if (showDpiWarning) { - MONITORINFOEX mi; - mi.cbSize = sizeof(mi); - - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { - GetMonitorInfo(monitor, &mi); - } }) - .wait(); - - const auto x = mi.rcWork.left; - const auto y = mi.rcWork.top; - const auto width = mi.rcWork.right - mi.rcWork.left; - const auto height = mi.rcWork.bottom - mi.rcWork.top; - editorLocation = - std::to_wstring(x) + L"_" + - std::to_wstring(y) + L"_" + - std::to_wstring(width) + L"_" + - std::to_wstring(height); + MessageBoxW(NULL, + GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(), + GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + MB_OK | MB_ICONWARNING); } const auto& fancyZonesData = FancyZonesDataInstance(); - - if (!fancyZonesData.SerializeDeviceInfoToTmpFile(zoneWindow->UniqueId())) - { - return; - } - - const std::wstring params = - /*1*/ editorLocation + L" " + - /*2*/ L"\"" + std::to_wstring(GetCurrentProcessId()) + L"\""; + fancyZonesData.SerializeDeviceInfoToTmpFile(m_currentDesktopId); SHELLEXECUTEINFO sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; @@ -930,11 +896,11 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId) n if (monitor) { - uniqueId = ZoneWindowUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); + uniqueId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); } else { - uniqueId = ZoneWindowUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()); + uniqueId = FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()); } std::wstring parentId{}; diff --git a/src/modules/fancyzones/lib/FancyZonesData.cpp b/src/modules/fancyzones/lib/FancyZonesData.cpp index 28c2beddaf..435b44d345 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.cpp +++ b/src/modules/fancyzones/lib/FancyZonesData.cpp @@ -478,47 +478,41 @@ void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const FancyZ } } -bool FancyZonesData::SerializeDeviceInfoToTmpFile(const std::wstring& uniqueId) const +void FancyZonesData::SerializeDeviceInfoToTmpFile(const GUID& currentVirtualDesktop) const { - const auto deviceInfo = FindDeviceInfo(uniqueId); - if (!deviceInfo.has_value()) - { - return false; - } - - JSONHelpers::DeviceInfoJSON deviceInfoJson{ uniqueId, *deviceInfo }; - JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoJson, activeZoneSetTmpFileName); - - return true; + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, currentVirtualDesktop, activeZoneSetTmpFileName); } void FancyZonesData::ParseDataFromTmpFiles() { ParseDeviceInfoFromTmpFile(activeZoneSetTmpFileName); ParseDeletedCustomZoneSetsFromTmpFile(deletedCustomZoneSetsTmpFileName); - ParseCustomZoneSetFromTmpFile(appliedZoneSetTmpFileName); + ParseCustomZoneSetsFromTmpFile(appliedZoneSetTmpFileName); SaveFancyZonesData(); } void FancyZonesData::ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath) { std::scoped_lock lock{ dataLock }; - const auto& deviceInfo = JSONHelpers::ParseDeviceInfoFromTmpFile(tmpFilePath); + const auto& appliedZonesets = JSONHelpers::ParseDeviceInfoFromTmpFile(tmpFilePath); - if (deviceInfo) + if (appliedZonesets) { - deviceInfoMap[deviceInfo->deviceId] = std::move(deviceInfo->data); + for (const auto& zoneset : *appliedZonesets) + { + deviceInfoMap[zoneset.first] = std::move(zoneset.second); + } } } -void FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath) +void FancyZonesData::ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath) { std::scoped_lock lock{ dataLock }; - const auto& customZoneSet = JSONHelpers::ParseCustomZoneSetFromTmpFile(tmpFilePath); + const auto& customZoneSets = JSONHelpers::ParseCustomZoneSetsFromTmpFile(tmpFilePath); - if (customZoneSet) + for (const auto& zoneSet : customZoneSets) { - customZoneSetsMap[customZoneSet->uuid] = std::move(customZoneSet->data); + customZoneSetsMap[zoneSet.uuid] = zoneSet.data; } } diff --git a/src/modules/fancyzones/lib/FancyZonesData.h b/src/modules/fancyzones/lib/FancyZonesData.h index f08f740e9f..74babcd3cd 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.h +++ b/src/modules/fancyzones/lib/FancyZonesData.h @@ -15,6 +15,7 @@ namespace FancyZonesDataTypes { struct ZoneSetData; + struct DeviceIdData; struct DeviceInfoData; struct CustomZoneSetData; struct AppZoneHistoryData; @@ -71,7 +72,7 @@ public: void SetActiveZoneSet(const std::wstring& deviceId, const FancyZonesDataTypes::ZoneSetData& zoneSet); - bool SerializeDeviceInfoToTmpFile(const std::wstring& uniqueId) const; + void SerializeDeviceInfoToTmpFile(const GUID& currentVirtualDesktop) const; void ParseDataFromTmpFiles(); json::JsonObject GetPersistFancyZonesJSON(); @@ -113,7 +114,7 @@ private: } #endif void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - void ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath); + void ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); void ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); void RemoveDesktopAppZoneHistory(const std::wstring& desktopId); diff --git a/src/modules/fancyzones/lib/FancyZonesDataTypes.h b/src/modules/fancyzones/lib/FancyZonesDataTypes.h index 7d4cc60859..c2221fb6ce 100644 --- a/src/modules/fancyzones/lib/FancyZonesDataTypes.h +++ b/src/modules/fancyzones/lib/FancyZonesDataTypes.h @@ -108,6 +108,15 @@ namespace FancyZonesDataTypes std::vector zoneIndexSet; }; + struct DeviceIdData + { + std::wstring deviceName; + int width; + int height; + GUID virtualDesktopId; + std::wstring monitorId; + }; + struct DeviceInfoData { ZoneSetData activeZoneSet; diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index a8789e2067..68feee33e6 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -15,12 +15,14 @@ namespace NonLocalizable { const wchar_t ActiveZoneSetStr[] = L"active-zoneset"; + const wchar_t AppliedZonesets[] = L"applied-zonesets"; const wchar_t AppPathStr[] = L"app-path"; const wchar_t AppZoneHistoryStr[] = L"app-zone-history"; const wchar_t CanvasStr[] = L"canvas"; const wchar_t CellChildMapStr[] = L"cell-child-map"; const wchar_t ColumnsPercentageStr[] = L"columns-percentage"; const wchar_t ColumnsStr[] = L"columns"; + const wchar_t CreatedCustomZoneSets[] = L"created-custom-zone-sets"; const wchar_t CustomZoneSetsStr[] = L"custom-zone-sets"; const wchar_t DeletedCustomZoneSetsStr[] = L"deleted-custom-zone-sets"; const wchar_t DeviceIdStr[] = L"device-id"; @@ -445,6 +447,64 @@ namespace JSONHelpers } } + json::JsonObject AppliedZonesetsJSON::ToJson(const TDeviceInfoMap& deviceInfoMap) + { + json::JsonObject result{}; + + json::JsonArray array; + for (const auto& info : deviceInfoMap) + { + JSONHelpers::DeviceInfoJSON deviceInfoJson{ info.first, info.second }; + array.Append(JSONHelpers::DeviceInfoJSON::ToJson(deviceInfoJson)); + } + + result.SetNamedValue(NonLocalizable::AppliedZonesets, array); + return result; + } + + json::JsonObject AppliedZonesetsJSON::ToJson(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop) + { + json::JsonObject result{}; + + json::JsonArray array; + for (const auto& info : deviceInfoMap) + { + std::optional id = FancyZonesUtils::ParseDeviceId(info.first); + if (id.has_value() && id->virtualDesktopId == currentVirtualDesktop) + { + JSONHelpers::DeviceInfoJSON deviceInfoJson{ info.first, info.second }; + array.Append(JSONHelpers::DeviceInfoJSON::ToJson(deviceInfoJson)); + } + } + + result.SetNamedValue(NonLocalizable::AppliedZonesets, array); + return result; + } + + std::optional AppliedZonesetsJSON::FromJson(const json::JsonObject& json) + { + try + { + std::unordered_map appliedZonesets; + + auto zonesets = json.GetNamedArray(NonLocalizable::AppliedZonesets); + for (const auto& zoneset : zonesets) + { + std::optional device = DeviceInfoJSON::FromJson(zoneset.GetObjectW()); + if (device.has_value()) + { + appliedZonesets.insert(std::make_pair(device->deviceId, device->data)); + } + } + + return appliedZonesets; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + json::JsonObject GetPersistFancyZonesJSON(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName) { auto result = json::from_file(zonesSettingsFileName); @@ -603,20 +663,34 @@ namespace JSONHelpers return customZoneSetsJSON; } - void SerializeDeviceInfoToTmpFile(const JSONHelpers::DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) + void SerializeDeviceInfoToTmpFile(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop, std::wstring_view tmpFilePath) { - json::JsonObject deviceInfoJson = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); - json::to_file(tmpFilePath, deviceInfoJson); + json::to_file(tmpFilePath, JSONHelpers::AppliedZonesetsJSON::ToJson(deviceInfoMap, currentVirtualDesktop)); } - std::optional ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath) + void SerializeCustomZoneSetsToTmpFile(const TCustomZoneSetsMap& customZoneSetsMap, std::wstring_view tmpFilePath) { - std::optional result{ std::nullopt }; + json::JsonObject result{}; + + json::JsonArray array; + for (const auto& zoneSet : customZoneSetsMap) + { + CustomZoneSetJSON json{ zoneSet.first, zoneSet.second }; + array.Append(JSONHelpers::CustomZoneSetJSON::ToJson(json)); + } + + result.SetNamedValue(NonLocalizable::CreatedCustomZoneSets, array); + json::to_file(tmpFilePath, result); + } + + std::optional ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath) + { + std::optional result{ std::nullopt }; if (std::filesystem::exists(tmpFilePath)) { if (auto zoneSetJson = json::from_file(tmpFilePath); zoneSetJson.has_value()) { - if (auto deviceInfo = JSONHelpers::DeviceInfoJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value()) + if (auto deviceInfo = JSONHelpers::AppliedZonesetsJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value()) { result = std::move(deviceInfo); } @@ -627,24 +701,27 @@ namespace JSONHelpers return result; } - std::optional ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath) + std::vector ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath) { - std::optional result{ std::nullopt }; + std::vector result; if (std::filesystem::exists(tmpFilePath)) { try { if (auto customZoneSetJson = json::from_file(tmpFilePath); customZoneSetJson.has_value()) { - if (auto customZoneSet = JSONHelpers::CustomZoneSetJSON::FromJson(customZoneSetJson.value()); customZoneSet.has_value()) + auto zoneSetArray = customZoneSetJson.value().GetNamedArray(NonLocalizable::CreatedCustomZoneSets); + for (const auto& zoneSet : zoneSetArray) { - result = std::move(customZoneSet); + if (auto customZoneSet = JSONHelpers::CustomZoneSetJSON::FromJson(zoneSet.GetObjectW()); customZoneSet.has_value()) + { + result.emplace_back(std::move(*customZoneSet)); + } } } } catch (const winrt::hresult_error&) { - result = std::nullopt; } DeleteTmpFile(tmpFilePath); @@ -676,4 +753,4 @@ namespace JSONHelpers return result; } -} +} \ No newline at end of file diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index b42bd305e3..cf3dceb578 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -46,7 +46,6 @@ namespace JSONHelpers static std::optional FromJson(const json::JsonObject& zoneSet); }; - struct DeviceInfoJSON { std::wstring deviceId; @@ -60,6 +59,13 @@ namespace JSONHelpers using TDeviceInfoMap = std::unordered_map; using TCustomZoneSetsMap = std::unordered_map; + struct AppliedZonesetsJSON + { + static json::JsonObject ToJson(const TDeviceInfoMap& deviceInfoMap); + static json::JsonObject ToJson(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop); + static std::optional FromJson(const json::JsonObject& json); + }; + json::JsonObject GetPersistFancyZonesJSON(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName); void SaveFancyZonesData(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName, @@ -76,8 +82,13 @@ namespace JSONHelpers TCustomZoneSetsMap ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON); json::JsonArray SerializeCustomZoneSets(const TCustomZoneSetsMap& customZoneSetsMap); - void SerializeDeviceInfoToTmpFile(const JSONHelpers::DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath); - std::optional ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - std::optional ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath); + void SerializeDeviceInfoToTmpFile(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop, std::wstring_view tmpFilePath); + std::optional ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); + std::vector ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); std::vector ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); + +#if defined(UNIT_TESTS) + void SerializeCustomZoneSetsToTmpFile(const TCustomZoneSetsMap& customZoneSetsMap, std::wstring_view tmpFilePath); +#endif + } diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 672a77723e..5dfd6c2999 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -25,44 +25,6 @@ namespace NonLocalizable using namespace FancyZonesUtils; -namespace ZoneWindowUtils -{ - std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& deviceId, const std::wstring& virtualDesktopId) - { - MONITORINFOEXW mi; - mi.cbSize = sizeof(mi); - if (!virtualDesktopId.empty() && GetMonitorInfo(monitor, &mi)) - { - Rect const monitorRect(mi.rcMonitor); - // Unique identifier format: ___ - return ParseDeviceId(deviceId) + - L'_' + - std::to_wstring(monitorRect.width()) + - L'_' + - std::to_wstring(monitorRect.height()) + - L'_' + - virtualDesktopId; - } - return {}; - } - - std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId) - { - std::wstring result{ ZonedWindowProperties::MultiMonitorDeviceID }; - - RECT combinedResolution = GetAllMonitorsCombinedRect<&MONITORINFO::rcMonitor>(); - - result += L'_'; - result += std::to_wstring(combinedResolution.right - combinedResolution.left); - result += L'_'; - result += std::to_wstring(combinedResolution.bottom - combinedResolution.top); - result += L'_'; - result += virtualDesktopId; - - return result; - } -} - struct ZoneWindow : public winrt::implements { public: diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 9eb70d9c71..26dc63c9ad 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -2,12 +2,6 @@ #include "FancyZones.h" #include "lib/ZoneSet.h" -namespace ZoneWindowUtils -{ - std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& devideId, const std::wstring& virtualDesktopId); - std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId); -} - /** * Class representing single work area, which is defined by monitor and virtual desktop. */ diff --git a/src/modules/fancyzones/lib/util.cpp b/src/modules/fancyzones/lib/util.cpp index 5a35d4a993..99b1a445dd 100644 --- a/src/modules/fancyzones/lib/util.cpp +++ b/src/modules/fancyzones/lib/util.cpp @@ -9,6 +9,8 @@ #include #include +#include + // Non-Localizable strings namespace NonLocalizable { @@ -40,7 +42,7 @@ namespace namespace FancyZonesUtils { - std::wstring ParseDeviceId(const std::wstring& deviceId) + std::wstring TrimDeviceId(const std::wstring& deviceId) { // We're interested in the unique part between the first and last #'s // Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} @@ -63,7 +65,95 @@ namespace FancyZonesUtils return defaultDeviceId; } } + + std::optional ParseDeviceId(const std::wstring& str) + { + FancyZonesDataTypes::DeviceIdData data; + std::wstring temp; + std::wstringstream wss(str); + + /* + Important fix for device info that contains a '_' in the name: + 1. first search for '#' + 2. Then split the remaining string by '_' + */ + + // Step 1: parse the name until the #, then to the '_' + if (str.find(L'#') != std::string::npos) + { + std::getline(wss, temp, L'#'); + + data.deviceName = temp; + + if (!std::getline(wss, temp, L'_')) + { + return std::nullopt; + } + + data.deviceName += L"#" + temp; + } + else if(std::getline(wss, temp, L'_') && !temp.empty()) + { + data.deviceName = temp; + } + else + { + return std::nullopt; + } + + // Step 2: parse the rest of the id + std::vector parts; + while (std::getline(wss, temp, L'_')) + { + parts.push_back(temp); + } + + if (parts.size() != 3 && parts.size() != 4) + { + return std::nullopt; + } + + /* + Refer to ZoneWindowUtils::GenerateUniqueId parts contain: + 1. monitor id [string] + 2. width of device [int] + 3. height of device [int] + 4. virtual desktop id (GUID) [string] + */ + try + { + for (const auto& c : parts[0]) + { + std::stoi(std::wstring(&c)); + } + + for (const auto& c : parts[1]) + { + std::stoi(std::wstring(&c)); + } + + data.width = std::stoi(parts[0]); + data.height = std::stoi(parts[1]); + } + catch (const std::exception&) + { + return std::nullopt; + } + + if (!SUCCEEDED(CLSIDFromString(parts[2].c_str(), &data.virtualDesktopId))) + { + return std::nullopt; + } + + if (parts.size() == 4) + { + data.monitorId = parts[3]; //could be empty + } + + return data; + } + typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*); UINT GetDpiForMonitor(HMONITOR monitor) noexcept { @@ -457,6 +547,71 @@ namespace FancyZonesUtils return true; } + std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& deviceId, const std::wstring& virtualDesktopId) + { + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (!virtualDesktopId.empty() && GetMonitorInfo(monitor, &mi)) + { + Rect const monitorRect(mi.rcMonitor); + // Unique identifier format: ___ + return TrimDeviceId(deviceId) + + L'_' + + std::to_wstring(monitorRect.width()) + + L'_' + + std::to_wstring(monitorRect.height()) + + L'_' + + virtualDesktopId; + } + return {}; + } + + std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId) + { + std::wstring result{ ZonedWindowProperties::MultiMonitorDeviceID }; + + RECT combinedResolution = GetAllMonitorsCombinedRect<&MONITORINFO::rcMonitor>(); + + result += L'_'; + result += std::to_wstring(combinedResolution.right - combinedResolution.left); + result += L'_'; + result += std::to_wstring(combinedResolution.bottom - combinedResolution.top); + result += L'_'; + result += virtualDesktopId; + + return result; + } + + std::optional GenerateMonitorId(MONITORINFOEX mi, HMONITOR monitor, const GUID& virtualDesktopId) + { + DISPLAY_DEVICE displayDevice = { sizeof(displayDevice) }; + PCWSTR deviceId = nullptr; + + bool validMonitor = true; + if (EnumDisplayDevices(mi.szDevice, 0, &displayDevice, 1)) + { + if (displayDevice.DeviceID[0] != L'\0') + { + deviceId = displayDevice.DeviceID; + } + } + + if (!deviceId) + { + deviceId = GetSystemMetrics(SM_REMOTESESSION) ? + L"\\\\?\\DISPLAY#REMOTEDISPLAY#" : + L"\\\\?\\DISPLAY#LOCALDISPLAY#"; + } + + wil::unique_cotaskmem_string vdId; + if (SUCCEEDED(StringFromCLSID(virtualDesktopId, &vdId))) + { + return GenerateUniqueId(monitor, deviceId, vdId.get()); + } + + return std::nullopt; + } + size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector& zoneRects) noexcept { using complex = std::complex; diff --git a/src/modules/fancyzones/lib/util.h b/src/modules/fancyzones/lib/util.h index 8f2cffed1e..7eec65b77c 100644 --- a/src/modules/fancyzones/lib/util.h +++ b/src/modules/fancyzones/lib/util.h @@ -3,6 +3,11 @@ #include "gdiplus.h" #include +namespace FancyZonesDataTypes +{ + struct DeviceIdData; +} + namespace FancyZonesUtils { struct Rect @@ -131,6 +136,28 @@ namespace FancyZonesUtils return result; } + template + std::vector> GetAllMonitorInfo() + { + using result_t = std::vector>; + result_t result; + + auto enumMonitors = [](HMONITOR monitor, HDC hdc, LPRECT pRect, LPARAM param) -> BOOL { + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); + result_t& result = *reinterpret_cast(param); + if (GetMonitorInfo(monitor, &mi)) + { + result.push_back({ monitor, mi }); + } + + return TRUE; + }; + + EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast(&result)); + return result; + } + template RECT GetAllMonitorsCombinedRect() { @@ -157,8 +184,6 @@ namespace FancyZonesUtils return result; } - std::wstring ParseDeviceId(const std::wstring& deviceId); - UINT GetDpiForMonitor(HMONITOR monitor) noexcept; void OrderMonitors(std::vector>& monitorInfo); void SizeWindowToRect(HWND window, RECT rect) noexcept; @@ -174,6 +199,13 @@ namespace FancyZonesUtils void RestoreWindowOrigin(HWND window) noexcept; bool IsValidGuid(const std::wstring& str); + + std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& devideId, const std::wstring& virtualDesktopId); + std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId); + std::optional GenerateMonitorId(MONITORINFOEX mi, HMONITOR monitor, const GUID& virtualDesktopId); + + std::wstring TrimDeviceId(const std::wstring& deviceId); + std::optional ParseDeviceId(const std::wstring& deviceId); bool IsValidDeviceId(const std::wstring& str); RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept; diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index ff2c2ac688..61b1a1415f 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -938,6 +938,92 @@ namespace FancyZonesUnitTests } }; + TEST_CLASS(AppliedZonesetsUnitTests) + { + TEST_METHOD(SingleDevice) + { + const std::wstring deviceId = L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1539}"; + const std::wstring zoneUuid = L"{33A2B101-06E0-437B-A61E-CDBECF502906}"; + const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; + DeviceInfoData data{ ZoneSetData{ zoneUuid, type }, true, 10, 4 }; + + TDeviceInfoMap expected; + expected.insert(std::make_pair(deviceId, data)); + + json::JsonObject json = AppliedZonesetsJSON::ToJson(expected); + auto actual = AppliedZonesetsJSON::FromJson(json); + + Assert::IsTrue(actual.has_value()); + Assert::AreEqual(expected.size(), actual->size()); + for (const auto& exp : expected) + { + Assert::IsTrue(actual->contains(exp.first)); + + const auto act = actual->find(exp.first); + Assert::AreEqual(exp.second.zoneCount, act->second.zoneCount); + Assert::AreEqual(exp.second.showSpacing, act->second.showSpacing); + Assert::AreEqual(exp.second.spacing, act->second.spacing); + Assert::AreEqual(exp.second.activeZoneSet.uuid, act->second.activeZoneSet.uuid); + Assert::AreEqual((int)exp.second.activeZoneSet.type, (int)act->second.activeZoneSet.type); + } + } + + TEST_METHOD (MultipleDevices) + { + TDeviceInfoMap expected; + expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1539}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Columns }, true, 10, 4 })); + expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1538}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502905}", ZoneSetLayoutType::Rows }, false, 8, 5 })); + expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1537}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502904}", ZoneSetLayoutType::Grid }, true, 9, 6 })); + + json::JsonObject json = AppliedZonesetsJSON::ToJson(expected); + auto actual = AppliedZonesetsJSON::FromJson(json); + + Assert::IsTrue(actual.has_value()); + Assert::AreEqual(expected.size(), actual->size()); + for (const auto& exp : expected) + { + Assert::IsTrue(actual->contains(exp.first)); + + const auto act = actual->find(exp.first); + Assert::AreEqual(exp.second.zoneCount, act->second.zoneCount); + Assert::AreEqual(exp.second.showSpacing, act->second.showSpacing); + Assert::AreEqual(exp.second.spacing, act->second.spacing); + Assert::AreEqual(exp.second.activeZoneSet.uuid, act->second.activeZoneSet.uuid); + Assert::AreEqual((int)exp.second.activeZoneSet.type, (int)act->second.activeZoneSet.type); + } + } + + TEST_METHOD (FromJsonNoDeviceId) + { + json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": [{\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{81B9FCD3-88CA-4B21-A681-5D1129A1527F}\",\"type\": \"grid\"},\"editor-show-spacing\": true,\"editor-spacing\": 5,\"editor-zone-count\": 4},{\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{8110E0D5-4815-4A35-A5AC-DF82A65FF58B}\",\"type\": \"priority-grid\"},\"editor-show-spacing\": false,\"editor-spacing\": 6,\"editor-zone-count\": 2}]}"); + auto actual = AppliedZonesetsJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + Assert::IsTrue(actual->empty()); + } + + TEST_METHOD (FromInvalidJsonNotArray) + { + json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": {\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{81B9FCD3-88CA-4B21-A681-5D1129A1527F}\",\"type\": \"grid\"},\"editor-show-spacing\": true,\"editor-spacing\": 5,\"editor-zone-count\": 4}}"); + auto actual = AppliedZonesetsJSON::FromJson(json); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (FromEmptyJson) + { + json::JsonObject json = json::JsonObject::Parse(L"{}"); + auto actual = AppliedZonesetsJSON::FromJson(json); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (FromEmptyDeviceArray) + { + json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": []}"); + auto actual = AppliedZonesetsJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + Assert::IsTrue(actual->empty()); + } + }; + TEST_CLASS (FancyZonesDataUnitTests) { private: @@ -947,6 +1033,8 @@ namespace FancyZonesUnitTests const json::JsonValue m_defaultCustomDeviceValue = json::JsonValue::Parse(m_defaultCustomDeviceStr); const json::JsonObject m_defaultCustomDeviceObj = json::JsonObject::Parse(m_defaultCustomDeviceStr); + GUID m_defaultVDId; + HINSTANCE m_hInst{}; FancyZonesData& m_fzData = FancyZonesDataInstance(); @@ -964,6 +1052,10 @@ namespace FancyZonesUnitTests m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); m_fzData.clear_data(); std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName)); + + auto guid = Helpers::StringToGuid(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}"); + Assert::IsTrue(guid.has_value()); + m_defaultVDId = *guid; } TEST_METHOD_CLEANUP(CleanUp) @@ -1098,15 +1190,18 @@ namespace FancyZonesUnitTests { FancyZonesData data; data.SetSettingsModulePath(m_moduleName); - DeviceInfoJSON deviceInfo{ L"default_device_id", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + + TDeviceInfoMap deviceInfoMap; + DeviceInfoData deviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }; + deviceInfoMap.insert(std::make_pair(m_defaultDeviceId, deviceInfoData)); const std::wstring path = data.zonesSettingsFileName + L".test_tmp"; - JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, path); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_defaultVDId, path); bool actualFileExists = std::filesystem::exists(path); Assert::IsTrue(actualFileExists); - auto expectedData = DeviceInfoJSON::ToJson(deviceInfo); + auto expectedData = AppliedZonesetsJSON::ToJson(deviceInfoMap); auto actualSavedData = json::from_file(path); std::filesystem::remove(path); //clean up before compare asserts @@ -1118,10 +1213,15 @@ namespace FancyZonesUnitTests { FancyZonesData data; data.SetSettingsModulePath(m_moduleName); + const std::wstring deviceId = m_defaultDeviceId; - DeviceInfoJSON expected{ deviceId, DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + DeviceInfoData expected{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 }; + + TDeviceInfoMap expectedDeviceInfoMap; + expectedDeviceInfoMap.insert(std::make_pair(deviceId, expected)); + const std::wstring path = data.zonesSettingsFileName + L".test_tmp"; - JSONHelpers::SerializeDeviceInfoToTmpFile(expected, path); + JSONHelpers::SerializeDeviceInfoToTmpFile(expectedDeviceInfoMap, m_defaultVDId, path); data.ParseDeviceInfoFromTmpFile(path); @@ -1136,11 +1236,11 @@ namespace FancyZonesUnitTests Assert::AreEqual((size_t)1, devices.size()); auto actual = devices.find(deviceId)->second; - Assert::AreEqual(expected.data.showSpacing, actual.showSpacing); - Assert::AreEqual(expected.data.spacing, actual.spacing); - Assert::AreEqual(expected.data.zoneCount, actual.zoneCount); - Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual.activeZoneSet.type); - Assert::AreEqual(expected.data.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); + Assert::AreEqual(expected.showSpacing, actual.showSpacing); + Assert::AreEqual(expected.spacing, actual.spacing); + Assert::AreEqual(expected.zoneCount, actual.zoneCount); + Assert::AreEqual((int)expected.activeZoneSet.type, (int)actual.activeZoneSet.type); + Assert::AreEqual(expected.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); } TEST_METHOD (DeviceInfoReadTempNonexistent) @@ -1525,9 +1625,12 @@ namespace FancyZonesUnitTests const std::wstring deviceId = L"default_device_id"; { - DeviceInfoJSON deviceInfo{ deviceId, DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + TDeviceInfoMap deviceInfoMap; + DeviceInfoData deviceInfoData { ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 }; + deviceInfoMap.insert(std::make_pair(deviceId, deviceInfoData)); + const std::wstring deviceInfoPath = m_fzData.zonesSettingsFileName + L".device_info_tmp"; - JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_defaultVDId, deviceInfoPath); m_fzData.ParseDeviceInfoFromTmpFile(deviceInfoPath); std::filesystem::remove(deviceInfoPath); @@ -1540,13 +1643,18 @@ namespace FancyZonesUnitTests .rowsPercents = { 10000 }, .columnsPercents = { 2500, 5000, 2500 }, .cellChildMap = { { 0, 1, 2 } } })); - CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid }; + CustomZoneSetJSON expected{ uuid, zoneSetData }; FancyZonesData data; data.SetSettingsModulePath(m_moduleName); const std::wstring path = data.zonesSettingsFileName + L".test_tmp"; - json::to_file(path, CustomZoneSetJSON::ToJson(expected)); - m_fzData.ParseCustomZoneSetFromTmpFile(path); + + TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(uuid, zoneSetData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, path); + + m_fzData.ParseCustomZoneSetsFromTmpFile(path); bool actualFileExists = std::filesystem::exists(path); if (actualFileExists) @@ -1572,7 +1680,7 @@ namespace FancyZonesUnitTests const std::wstring path = m_fzData.zonesSettingsFileName + L".test_tmp"; const std::wstring deviceId = L"default_device_id"; - m_fzData.ParseCustomZoneSetFromTmpFile(path); + m_fzData.ParseCustomZoneSetsFromTmpFile(path); auto devices = m_fzData.GetDeviceInfoMap(); Assert::AreEqual((size_t)0, devices.size()); } diff --git a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp index c06f344136..1ef608d6db 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp @@ -40,28 +40,143 @@ namespace FancyZonesUnitTests TEST_CLASS(UtilUnitTests) { - TEST_METHOD(TestParseDeviceId) + TEST_METHOD (TestTrimDeviceId) { // We're interested in the unique part between the first and last #'s // Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} // Example output: DELA026#5&10a58c63&0&UID16777488 const std::wstring input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"; - const std::wstring actual = ParseDeviceId(input); + const std::wstring actual = TrimDeviceId(input); const std::wstring expected = L"DELA026#5&10a58c63&0&UID16777488"; Assert::AreEqual(expected, actual); } - TEST_METHOD(TestParseInvalidDeviceId) + TEST_METHOD(TestTrimInvalidDeviceId) { // We're interested in the unique part between the first and last #'s // Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} // Example output: DELA026#5&10a58c63&0&UID16777488 const std::wstring input = L"AnInvalidDeviceId"; - const std::wstring actual = ParseDeviceId(input); + const std::wstring actual = TrimDeviceId(input); const std::wstring expected = L"FallbackDevice"; Assert::AreEqual(expected, actual); } + TEST_METHOD(TestParseDeviceId01) + { + const std::wstring input = L"AOC0001#5&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + + GUID guid; + const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + CLSIDFromString(expectedGuidStr, &guid); + const FancyZonesDataTypes::DeviceIdData expected{ L"AOC0001#5&37ac4db&0&UID160002", 1536, 960, guid }; + + const auto actual = ParseDeviceId(input); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.deviceName, actual->deviceName); + Assert::AreEqual(expected.height, actual->height); + Assert::AreEqual(expected.width, actual->width); + Assert::AreEqual(expected.monitorId, actual->monitorId); + + wil::unique_cotaskmem_string actualGuidStr; + StringFromCLSID(actual->virtualDesktopId, &actualGuidStr); + Assert::AreEqual(expectedGuidStr, actualGuidStr.get()); + } + + TEST_METHOD (TestParseDeviceId02) + { + const std::wstring input = L"AOC0001#5&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}_monitorId"; + + GUID guid; + const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + CLSIDFromString(expectedGuidStr, &guid); + const FancyZonesDataTypes::DeviceIdData expected{ L"AOC0001#5&37ac4db&0&UID160002", 1536, 960, guid, L"monitorId" }; + + const auto actual = ParseDeviceId(input); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.deviceName, actual->deviceName); + Assert::AreEqual(expected.height, actual->height); + Assert::AreEqual(expected.width, actual->width); + Assert::AreEqual(expected.monitorId, actual->monitorId); + + wil::unique_cotaskmem_string actualGuidStr; + StringFromCLSID(actual->virtualDesktopId, &actualGuidStr); + Assert::AreEqual(expectedGuidStr, actualGuidStr.get()); + } + + TEST_METHOD (TestParseDeviceId03) + { + // difference with previous tests is in the device name: there is no # symbol + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + + GUID guid; + const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + CLSIDFromString(expectedGuidStr, &guid); + const FancyZonesDataTypes::DeviceIdData expected{ L"AOC00015&37ac4db&0&UID160002", 1536, 960, guid }; + + const auto actual = ParseDeviceId(input); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.deviceName, actual->deviceName); + Assert::AreEqual(expected.height, actual->height); + Assert::AreEqual(expected.width, actual->width); + Assert::AreEqual(expected.monitorId, actual->monitorId); + + wil::unique_cotaskmem_string actualGuidStr; + StringFromCLSID(actual->virtualDesktopId, &actualGuidStr); + Assert::AreEqual(expectedGuidStr, actualGuidStr.get()); + } + + TEST_METHOD (TestParseDeviceIdInvalid01) + { + // no width or height + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (TestParseDeviceIdInvalid02) + { + // no width and height + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_{E0A2904E-889C-4532-95B1-28FE15C16F66}_monitorId"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (TestParseDeviceIdInvalid03) + { + // no guid + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (TestParseDeviceIdInvalid04) + { + // invalid guid + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_{asdf}"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (TestParseDeviceIdInvalid05) + { + // invalid width/height + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_15a6_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD (TestParseDeviceIdInvalid06) + { + // changed order + const std::wstring input = L"AOC00015&37ac4db&0&UID160002_15a6_960_monitorId_{E0A2904E-889C-4532-95B1-28FE15C16F66}"; + const auto actual = ParseDeviceId(input); + Assert::IsFalse(actual.has_value()); + } + TEST_METHOD(TestMonitorOrdering01) { // Three horizontally arranged monitors, bottom aligned, with increasing sizes diff --git a/src/modules/fancyzones/tests/UnitTests/Util.cpp b/src/modules/fancyzones/tests/UnitTests/Util.cpp index 4f89409270..e113202da0 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.cpp +++ b/src/modules/fancyzones/tests/UnitTests/Util.cpp @@ -172,3 +172,14 @@ std::wstring Helpers::CreateGuidString() return L""; } + +std::optional Helpers::StringToGuid(const std::wstring& str) +{ + GUID guid; + if (CLSIDFromString(str.c_str(), &guid) == S_OK) + { + return guid; + } + + return std::nullopt; +} diff --git a/src/modules/fancyzones/tests/UnitTests/Util.h b/src/modules/fancyzones/tests/UnitTests/Util.h index d8058c8adf..4696fc5927 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.h +++ b/src/modules/fancyzones/tests/UnitTests/Util.h @@ -57,6 +57,7 @@ namespace Helpers { std::wstring GuidToString(const GUID& guid); std::wstring CreateGuidString(); + std::optional StringToGuid(const std::wstring& str); } template<> diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 1841255049..c0982166dd 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -2,6 +2,7 @@ #include "lib\FancyZonesData.h" #include "lib\FancyZonesDataTypes.h" #include "lib\JsonHelpers.h" +#include "lib\VirtualDesktopUtils.h" #include "lib\ZoneSet.h" #include @@ -1048,9 +1049,14 @@ namespace FancyZonesUnitTests //prepare device data { const std::wstring zoneUuid = L"default_device_id"; - JSONHelpers::DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 })); + + GUID virtualDesktopId{}; + Assert::IsTrue(VirtualDesktopUtils::GetCurrentVirtualDesktopId(&virtualDesktopId), L"Cannot create virtual desktop id"); const std::wstring deviceInfoPath = FancyZonesDataInstance().zonesSettingsFileName + L".device_info_tmp"; - JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, virtualDesktopId, deviceInfoPath); FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath); std::filesystem::remove(deviceInfoPath); @@ -1060,10 +1066,14 @@ namespace FancyZonesUnitTests wil::unique_cotaskmem_string uuid; Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; - JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; - json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); + CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Canvas, info }; + JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), zoneSetData }; + JSONHelpers::TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(uuid.get(), zoneSetData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, m_path); + Assert::IsTrue(std::filesystem::exists(m_path)); - FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path); + FancyZonesDataInstance().ParseCustomZoneSetsFromTmpFile(m_path); //test const int spacing = 10; @@ -1083,9 +1093,14 @@ namespace FancyZonesUnitTests //prepare device data { const std::wstring zoneUuid = L"default_device_id"; - JSONHelpers::DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 })); + + GUID virtualDesktopId{}; + Assert::IsTrue(VirtualDesktopUtils::GetCurrentVirtualDesktopId(&virtualDesktopId), L"Cannot create virtual desktop id"); const std::wstring deviceInfoPath = FancyZonesDataInstance().zonesSettingsFileName + L".device_info_tmp"; - JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, virtualDesktopId, deviceInfoPath); FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath); std::filesystem::remove(deviceInfoPath); @@ -1100,10 +1115,14 @@ namespace FancyZonesUnitTests .rowsPercents = { 10000 }, .columnsPercents = { 2500, 5000, 2500 }, .cellChildMap = { { 0, 1, 2 } } })); - JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; - json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); + CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid }; + JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), zoneSetData }; + JSONHelpers::TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(uuid.get(), zoneSetData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, m_path); + Assert::IsTrue(std::filesystem::exists(m_path)); - FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path); + FancyZonesDataInstance().ParseCustomZoneSetsFromTmpFile(m_path); const int spacing = 10; const int zoneCount = grid.rows() * grid.columns(); diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 913fcf530b..0cfaabab43 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -69,7 +69,8 @@ namespace FancyZonesUnitTests HINSTANCE m_hInst{}; HMONITOR m_monitor{}; - MONITORINFO m_monitorInfo{}; + MONITORINFOEX m_monitorInfo{}; + GUID m_virtualDesktopGuid{}; FancyZonesData& m_fancyZonesData = FancyZonesDataInstance(); @@ -102,6 +103,10 @@ namespace FancyZonesUnitTests m_fancyZonesData.SetSettingsModulePath(L"FancyZonesUnitTests"); m_fancyZonesData.clear_data(); + + auto guid = Helpers::StringToGuid(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}"); + Assert::IsTrue(guid.has_value()); + m_virtualDesktopGuid = *guid; } TEST_METHOD_CLEANUP(Cleanup) @@ -154,7 +159,7 @@ namespace FancyZonesUnitTests TEST_METHOD(CreateZoneWindowNoDeviceId) { // Generate unique id without device id - std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, {}, m_virtualDesktopId); + std::wstring uniqueId = FancyZonesUtils::GenerateUniqueId(m_monitor, {}, m_virtualDesktopId); auto zoneWindow = MakeZoneWindow(winrt::make_self().get(), m_hInst, m_monitor, uniqueId, {}); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); @@ -172,7 +177,7 @@ namespace FancyZonesUnitTests TEST_METHOD(CreateZoneWindowNoDesktopId) { // Generate unique id without virtual desktop id - std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, m_deviceId, {}); + std::wstring uniqueId = FancyZonesUtils::GenerateUniqueId(m_monitor, m_deviceId, {}); auto zoneWindow = MakeZoneWindow(winrt::make_self().get(), m_hInst, m_monitor, uniqueId, {}); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); @@ -219,9 +224,9 @@ namespace FancyZonesUnitTests const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; const auto expectedZoneSet = ZoneSetData{ Helpers::CreateGuidString(), type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; - const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; - const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); - json::to_file(activeZoneSetTempPath, json); + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data)); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath); m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); @@ -248,18 +253,20 @@ namespace FancyZonesUnitTests const auto customSetGuid = Helpers::CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; - const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; - const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); - json::to_file(activeZoneSetTempPath, json); - + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data)); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath); + const auto info = CanvasLayoutInfo{ 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } }; const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData }); - json::to_file(appliedZoneSetTempPath, customZoneJson); + JSONHelpers::TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(customSetGuid, customZoneData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath); m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); - m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); + m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(winrt::make_self().get(), m_hInst, m_monitor, m_uniqueId.str(), {}); @@ -285,9 +292,9 @@ namespace FancyZonesUnitTests const auto customSetGuid = Helpers::CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; - const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; - const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); - json::to_file(activeZoneSetTempPath, json); + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data)); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath); const auto info = CanvasLayoutInfo{ 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } @@ -295,7 +302,9 @@ namespace FancyZonesUnitTests const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; const auto customZoneSet = JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData }; auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(customZoneSet); - json::to_file(appliedZoneSetTempPath, customZoneJson); + JSONHelpers::TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(customSetGuid, customZoneData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath); //save same zone as deleted json::JsonObject deletedCustomZoneSets = {}; @@ -306,7 +315,7 @@ namespace FancyZonesUnitTests m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); - m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); + m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(winrt::make_self().get(), m_hInst, m_monitor, m_uniqueId.str(), {}); @@ -331,9 +340,9 @@ namespace FancyZonesUnitTests const auto customSetGuid = Helpers::CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; - const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; - const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); - json::to_file(activeZoneSetTempPath, json); + JSONHelpers::TDeviceInfoMap deviceInfoMap; + deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data)); + JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath); const auto info = CanvasLayoutInfo{ 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } @@ -341,7 +350,9 @@ namespace FancyZonesUnitTests const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; const auto customZoneSet = JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData }; auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(customZoneSet); - json::to_file(appliedZoneSetTempPath, customZoneJson); + JSONHelpers::TCustomZoneSetsMap customZoneSets; + customZoneSets.insert(std::make_pair(customSetGuid, customZoneData)); + JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath); //save different zone as deleted json::JsonObject deletedCustomZoneSets = {}; @@ -353,7 +364,7 @@ namespace FancyZonesUnitTests m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); - m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); + m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(winrt::make_self().get(), m_hInst, m_monitor, m_uniqueId.str(), {}); diff --git a/tools/FancyZonesEditor_DPI_netcore_test/App.xaml b/tools/FancyZonesEditor_DPI_netcore_test/App.xaml new file mode 100644 index 0000000000..d08b834106 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/tools/FancyZonesEditor_DPI_netcore_test/App.xaml.cs b/tools/FancyZonesEditor_DPI_netcore_test/App.xaml.cs new file mode 100644 index 0000000000..06cf0394ca --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace FancyZonesEditor_DPI_netcore_test +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/tools/FancyZonesEditor_DPI_netcore_test/AssemblyInfo.cs b/tools/FancyZonesEditor_DPI_netcore_test/AssemblyInfo.cs new file mode 100644 index 0000000000..8b5504ecfb --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.csproj b/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.csproj new file mode 100644 index 0000000000..9f34e72c0b --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.csproj @@ -0,0 +1,15 @@ + + + + WinExe + netcoreapp3.1 + true + true + app.manifest + + + + + + + \ No newline at end of file diff --git a/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.sln b/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.sln new file mode 100644 index 0000000000..b52d1334e0 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/FancyZonesEditor_DPI_netcore_test.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30621.155 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor_DPI_netcore_test", "FancyZonesEditor_DPI_netcore_test.csproj", "{1764AC88-EE10-4613-AB77-F213EF8043D6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1764AC88-EE10-4613-AB77-F213EF8043D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1764AC88-EE10-4613-AB77-F213EF8043D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1764AC88-EE10-4613-AB77-F213EF8043D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1764AC88-EE10-4613-AB77-F213EF8043D6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F95C5E3B-3EC1-42AC-A24C-6A9350B72807} + EndGlobalSection +EndGlobal diff --git a/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml b/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml new file mode 100644 index 0000000000..fdecd89eae --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml.cs b/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml.cs new file mode 100644 index 0000000000..ab9d91c9a3 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/MainWindow.xaml.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; +using Microsoft.VisualStudio.Utilities; + +namespace FancyZonesEditor_DPI_netcore_test +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + struct ScreenInfo + { + public int MonitorDPI { get; set; } + + public double WindowDPI { get; set; } + + public Rect Resolution { get; set; } + + public Rect WorkArea { get; set; } + + public override string ToString() + { + var resolution = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", Resolution.Left, Resolution.Top, Resolution.Width, Resolution.Height); + var workArea = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", WorkArea.Left, WorkArea.Top, WorkArea.Width, WorkArea.Height); + + return "Monitor DPI: " + MonitorDPI + " - Resolution: " + resolution + " - WorkArea: " + workArea; + } + } + + private List workAreaWindows; + + public MainWindow() + { + InitializeComponent(); + + double primaryMonitorDPI = 96f; + + var colors = new Brush[] { Brushes.Green, Brushes.Blue, Brushes.Red }; + + var screens = System.Windows.Forms.Screen.AllScreens; + List screenInfoList = new List(); + + workAreaWindows = new List(); + + var monitors = MonitorsInfo.GetMonitors(); + + for (int i = 0; i < screens.Length; i++) + { + if (screens[i].Primary) + { + double monitorDPI; + DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI); + primaryMonitorDPI = monitorDPI; + break; + } + } + + for (int i = 0; i < screens.Length; i++) + { + var monitor = monitors[i]; + ScreenInfo screenInfo = new ScreenInfo(); + var window = new OverlayWindow + { + Opacity = 0.8, + Background = colors[i % colors.Length], + BorderBrush = Brushes.White, + BorderThickness = new Thickness(4, 4, 4, 4) + }; + + // get monitor dpi + double monitorDPI; + DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI); + screenInfo.MonitorDPI = (int)monitorDPI; + + // resolution + screenInfo.Resolution = new Rect(monitor.MonitorInfo.monitor.left, monitor.MonitorInfo.monitor.top, + monitor.MonitorInfo.monitor.width, monitor.MonitorInfo.monitor.height); + + // work area + Rect workedArea = new Rect(monitor.MonitorInfo.work.left, monitor.MonitorInfo.work.top, + monitor.MonitorInfo.work.width, monitor.MonitorInfo.work.height); + + double scalePosition = 96f / primaryMonitorDPI; + workedArea.X *= scalePosition; + workedArea.Y *= scalePosition; + + double scaleSize = 96f / monitorDPI; + workedArea.Width *= scaleSize; + workedArea.Height *= scaleSize; + + screenInfo.WorkArea = workedArea; + + screenInfo.WindowDPI = window.GetDpiX(); + screenInfoList.Add(screenInfo); + + // open window + window.Left = workedArea.X; + window.Top = workedArea.Y; + window.Width = workedArea.Width; + window.Height = workedArea.Height; + + workAreaWindows.Add(window); + window.Show(); + } + + MonitorList.ItemsSource = screenInfoList; + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + foreach (OverlayWindow window in workAreaWindows) + { + window.Close(); + } + } + } +} diff --git a/tools/FancyZonesEditor_DPI_netcore_test/MonitorsInfo.cs b/tools/FancyZonesEditor_DPI_netcore_test/MonitorsInfo.cs new file mode 100644 index 0000000000..b68c73fc24 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/MonitorsInfo.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace FancyZonesEditor_DPI_netcore_test +{ + public class MonitorsInfo + { + /// + /// Rectangle + /// + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + + public int width + { + get + { + return right - left; + } + } + + public int height + { + get + { + return bottom - top; + } + } + } + + + /// + /// Monitor information. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MONITORINFO + { + public uint size; + public RECT monitor; + public RECT work; + public uint flags; + } + + /// + /// Monitor Enum Delegate + /// + /// A handle to the display monitor. + /// A handle to a device context. + /// A pointer to a RECT structure. + /// Application-defined data that EnumDisplayMonitors passes directly to the enumeration function. + /// + public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, + ref RECT lprcMonitor, IntPtr dwData); + + /// + /// Enumerates through the display monitors. + /// + /// A handle to a display device context that defines the visible region of interest. + /// A pointer to a RECT structure that specifies a clipping rectangle. + /// A pointer to a MonitorEnumProc application-defined callback function. + /// Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function. + /// + [DllImport("user32.dll")] + public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, + MonitorEnumDelegate lpfnEnum, IntPtr dwData); + + /// + /// Gets the monitor information. + /// + /// A handle to the display monitor of interest. + /// A pointer to a MONITORINFO instance created by this method. + /// + [DllImport("user32.dll")] + public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi); + + /// + /// Monitor information with handle interface. + /// + public interface IMonitorInfoWithHandle + { + IntPtr MonitorHandle { get; } + MONITORINFO MonitorInfo { get; } + } + + /// + /// Monitor information with handle. + /// + public class MonitorInfoWithHandle : IMonitorInfoWithHandle + { + /// + /// Gets the monitor handle. + /// + /// + /// The monitor handle. + /// + public IntPtr MonitorHandle { get; private set; } + + /// + /// Gets the monitor information. + /// + /// + /// The monitor information. + /// + public MONITORINFO MonitorInfo { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The monitor handle. + /// The monitor information. + public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo) + { + MonitorHandle = monitorHandle; + MonitorInfo = monitorInfo; + } + } + + /// + /// Monitor Enum Delegate + /// + /// A handle to the display monitor. + /// A handle to a device context. + /// A pointer to a RECT structure. + /// Application-defined data that EnumDisplayMonitors passes directly to the enumeration function. + /// + public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) + { + var mi = new MONITORINFO(); + mi.size = (uint)Marshal.SizeOf(mi); + GetMonitorInfo(hMonitor, ref mi); + + // Add to monitor info + _monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi)); + return true; + } + + /// + /// Gets the monitors. + /// + /// + public static MonitorInfoWithHandle[] GetMonitors() + { + _monitorInfos = new List(); + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero); + return _monitorInfos.ToArray(); + } + + private static List _monitorInfos; + } +} diff --git a/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml b/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml new file mode 100644 index 0000000000..f455217193 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml @@ -0,0 +1,14 @@ + + diff --git a/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml.cs b/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml.cs new file mode 100644 index 0000000000..5828179c7c --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/OverlayWindow.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace FancyZonesEditor_DPI_netcore_test +{ + /// + /// Interaction logic for OverlayWindow.xaml + /// + public partial class OverlayWindow : Window + { + public OverlayWindow() + { + InitializeComponent(); + } + } +} diff --git a/tools/FancyZonesEditor_DPI_netcore_test/app.manifest b/tools/FancyZonesEditor_DPI_netcore_test/app.manifest new file mode 100644 index 0000000000..ed2909e9e1 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_netcore_test/app.manifest @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PerMonitorV2 + + + + + + + diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.sln b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.sln new file mode 100644 index 0000000000..5c50ac7932 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30621.155 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor_DPI_test", "FancyZonesEditor_DPI_test\FancyZonesEditor_DPI_test.csproj", "{B1708F5E-78F8-4646-86B5-0E83E3C79860}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1708F5E-78F8-4646-86B5-0E83E3C79860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1708F5E-78F8-4646-86B5-0E83E3C79860}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1708F5E-78F8-4646-86B5-0E83E3C79860}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1708F5E-78F8-4646-86B5-0E83E3C79860}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B6C181B9-2BFF-4057-89D2-9BFDEBCC87F6} + EndGlobalSection +EndGlobal diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.config b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.config new file mode 100644 index 0000000000..dd82da42e4 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml new file mode 100644 index 0000000000..e662c82e58 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml.cs new file mode 100644 index 0000000000..e50dd14830 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace FancyZonesEditor_DPI_test +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.csproj b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.csproj new file mode 100644 index 0000000000..bd954c353c --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test.csproj @@ -0,0 +1,144 @@ + + + + + Debug + AnyCPU + {B1708F5E-78F8-4646-86B5-0E83E3C79860} + WinExe + FancyZonesEditor_DPI_test + FancyZonesEditor_DPI_test + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + + ..\packages\Microsoft.VisualStudio.DpiAwareness.6.7.30328\lib\net46\Microsoft.VisualStudio.DpiAwareness.dll + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + + + + OverlayWindow.xaml + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + False + Microsoft .NET Framework 4.7.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml new file mode 100644 index 0000000000..9dc523da41 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml.cs new file mode 100644 index 0000000000..043aee2cdd --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MainWindow.xaml.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; +using Microsoft.VisualStudio.Utilities; + +namespace FancyZonesEditor_DPI_test +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + struct ScreenInfo + { + public int MonitorDPI { get; set; } + + public double WindowDPI { get; set; } + + public Rect Resolution { get; set; } + + public Rect WorkArea { get; set; } + + public override string ToString() + { + var resolution = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", Resolution.Left, Resolution.Top, Resolution.Width, Resolution.Height); + var workArea = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", WorkArea.Left, WorkArea.Top, WorkArea.Width, WorkArea.Height); + + return "Monitor DPI: " + MonitorDPI + " - Resolution: " + resolution + " - WorkArea: " + workArea; + } + } + + private List workAreaWindows; + + public MainWindow() + { + InitializeComponent(); + + double primaryMonitorDPI = 96f; + + var colors = new Brush[] { Brushes.Green, Brushes.Blue, Brushes.Red }; + + var screens = System.Windows.Forms.Screen.AllScreens; + List screenInfoList = new List(); + + workAreaWindows = new List(); + + var monitors = MonitorsInfo.GetMonitors(); + + for (int i = 0; i < screens.Length; i++) + { + if (screens[i].Primary) + { + double monitorDPI; + DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI); + primaryMonitorDPI = monitorDPI; + break; + } + } + + for (int i = 0; i < screens.Length; i++) + { + var monitor = monitors[i]; + ScreenInfo screenInfo = new ScreenInfo(); + var window = new OverlayWindow + { + Opacity = 0.8, + Background = colors[i % colors.Length], + BorderBrush = Brushes.White, + BorderThickness = new Thickness(4, 4, 4, 4) + }; + + // get monitor dpi + double monitorDPI; + DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI); + screenInfo.MonitorDPI = (int)monitorDPI; + + // screen resolution + screenInfo.Resolution = new Rect(monitor.MonitorInfo.monitor.left, monitor.MonitorInfo.monitor.top, + monitor.MonitorInfo.monitor.width, monitor.MonitorInfo.monitor.height); + + // work area + Rect workedArea = new Rect(monitor.MonitorInfo.work.left, monitor.MonitorInfo.work.top, + monitor.MonitorInfo.work.width, monitor.MonitorInfo.work.height); + + double scaleFactor = 96f / primaryMonitorDPI; + workedArea.X *= scaleFactor; + workedArea.Y *= scaleFactor; + workedArea.Width *= scaleFactor; + workedArea.Height *= scaleFactor; + + screenInfo.WorkArea = workedArea; + + screenInfo.WindowDPI = window.GetDpiX(); + screenInfoList.Add(screenInfo); + + // open window + window.Left = workedArea.X; + window.Top = workedArea.Y; + window.Width = workedArea.Width; + window.Height = workedArea.Height; + + workAreaWindows.Add(window); + window.Show(); + + } + + MonitorList.ItemsSource = screenInfoList; + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + foreach (OverlayWindow window in workAreaWindows) + { + window.Close(); + } + } + } +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MonitorInfo.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MonitorInfo.cs new file mode 100644 index 0000000000..c3adb09df6 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/MonitorInfo.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace FancyZonesEditor_DPI_test +{ + class MonitorsInfo + { + /// + /// Rectangle + /// + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + + public int width + { + get + { + return right - left; + } + } + + public int height + { + get + { + return bottom - top; + } + } + } + + + /// + /// Monitor information. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MONITORINFO + { + public uint size; + public RECT monitor; + public RECT work; + public uint flags; + } + + /// + /// Monitor Enum Delegate + /// + /// A handle to the display monitor. + /// A handle to a device context. + /// A pointer to a RECT structure. + /// Application-defined data that EnumDisplayMonitors passes directly to the enumeration function. + /// + public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, + ref RECT lprcMonitor, IntPtr dwData); + + /// + /// Enumerates through the display monitors. + /// + /// A handle to a display device context that defines the visible region of interest. + /// A pointer to a RECT structure that specifies a clipping rectangle. + /// A pointer to a MonitorEnumProc application-defined callback function. + /// Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function. + /// + [DllImport("user32.dll")] + public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, + MonitorEnumDelegate lpfnEnum, IntPtr dwData); + + /// + /// Gets the monitor information. + /// + /// A handle to the display monitor of interest. + /// A pointer to a MONITORINFO instance created by this method. + /// + [DllImport("user32.dll")] + public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi); + + /// + /// Monitor information with handle interface. + /// + public interface IMonitorInfoWithHandle + { + IntPtr MonitorHandle { get; } + MONITORINFO MonitorInfo { get; } + } + + /// + /// Monitor information with handle. + /// + public class MonitorInfoWithHandle : IMonitorInfoWithHandle + { + /// + /// Gets the monitor handle. + /// + /// + /// The monitor handle. + /// + public IntPtr MonitorHandle { get; private set; } + + /// + /// Gets the monitor information. + /// + /// + /// The monitor information. + /// + public MONITORINFO MonitorInfo { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The monitor handle. + /// The monitor information. + public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo) + { + MonitorHandle = monitorHandle; + MonitorInfo = monitorInfo; + } + } + + /// + /// Monitor Enum Delegate + /// + /// A handle to the display monitor. + /// A handle to a device context. + /// A pointer to a RECT structure. + /// Application-defined data that EnumDisplayMonitors passes directly to the enumeration function. + /// + public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) + { + var mi = new MONITORINFO(); + mi.size = (uint)Marshal.SizeOf(mi); + GetMonitorInfo(hMonitor, ref mi); + + // Add to monitor info + _monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi)); + return true; + } + + /// + /// Gets the monitors. + /// + /// + public static MonitorInfoWithHandle[] GetMonitors() + { + _monitorInfos = new List(); + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero); + return _monitorInfos.ToArray(); + } + + private static List _monitorInfos; + } +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml new file mode 100644 index 0000000000..56624884d8 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml @@ -0,0 +1,14 @@ + diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml.cs new file mode 100644 index 0000000000..cfeb750fc1 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/OverlayWindow.xaml.cs @@ -0,0 +1,22 @@ +using System; +using System.Windows; +using System.Windows.Media; + +namespace FancyZonesEditor_DPI_test +{ + /// + /// Interaction logic for OverlayWindow.xaml + /// + public partial class OverlayWindow : Window + { + public OverlayWindow() + { + InitializeComponent(); + } + + private void Window_DpiChanged(object sender, DpiChangedEventArgs e) + { + } + } + +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/AssemblyInfo.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3da7e63006 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FancyZonesEditor_DPI_test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FancyZonesEditor_DPI_test")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.Designer.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..056d87497c --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FancyZonesEditor_DPI_test.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FancyZonesEditor_DPI_test.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.resx b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.resx new file mode 100644 index 0000000000..af7dbebbac --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.Designer.cs b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..efde417872 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FancyZonesEditor_DPI_test.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.settings b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.settings new file mode 100644 index 0000000000..033d7a5e9e --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/app.manifest b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/app.manifest new file mode 100644 index 0000000000..26c1b51261 --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/app.manifest @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + System + + + + + + + diff --git a/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/packages.config b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/packages.config new file mode 100644 index 0000000000..1cb755e9da --- /dev/null +++ b/tools/FancyZonesEditor_DPI_test/FancyZonesEditor_DPI_test/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file