diff --git a/src/modules/fancyzones/dll/dllmain.cpp b/src/modules/fancyzones/dll/dllmain.cpp index 299081bf2b..08161f18e5 100644 --- a/src/modules/fancyzones/dll/dllmain.cpp +++ b/src/modules/fancyzones/dll/dllmain.cpp @@ -27,27 +27,26 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser return TRUE; } -// TODO: multimon support, need to pass the HMONITOR from the editor to here instead -// of using MonitorFromPoint // This function is exported and called from FancyZonesEditor.exe to save a layout from the editor. STDAPI PersistZoneSet( PCWSTR activeKey, // Registry key holding ActiveZoneSet - PCWSTR resolutionKey, // Registry key for screen resolution + PCWSTR resolutionKey, // Registry key to persist ZoneSet to + HMONITOR monitor, WORD layoutId, // LayoutModel Id int zoneCount, // Number of zones in zones int zones[]) // Array of zones serialized in left/top/right/bottom chunks { // See if we have already persisted this layout we can update. - UUID id{GUID_NULL}; - if (wil::unique_hkey key{ RegistryHelpers::OpenKey(resolutionKey) }) - { - ZoneSetPersistedData data{}; - DWORD dataSize = sizeof(data); - wchar_t value[256]{}; - DWORD valueLength = ARRAYSIZE(value); - DWORD i = 0; - while (RegEnumValueW(key.get(), i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) - { + UUID id{GUID_NULL}; + if (wil::unique_hkey key{ RegistryHelpers::OpenKey(resolutionKey) }) + { + ZoneSetPersistedData data{}; + DWORD dataSize = sizeof(data); + wchar_t value[256]{}; + DWORD valueLength = ARRAYSIZE(value); + DWORD i = 0; + while (RegEnumValueW(key.get(), i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) + { if (data.LayoutId == layoutId) { if (data.ZoneCount == zoneCount) @@ -73,7 +72,7 @@ STDAPI PersistZoneSet( ZoneSetConfig( id, layoutId, - MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY), + reinterpret_cast(monitor), resolutionKey, ZoneSetLayout::Custom, 0, 0, 0)); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index 6115581a74..9d2b719cad 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -20,7 +20,6 @@ namespace FancyZonesEditor private ushort _idInitial = 0; public App() { - //init settings _settings = new Settings(); } @@ -64,8 +63,7 @@ namespace FancyZonesEditor } foundModel.IsSelected = true; - // TODO: multimon - // Pass in the correct args to show on the desired monitor + EditorOverlay overlay = new EditorOverlay(); overlay.Show(); overlay.DataContext = foundModel; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs index ba3b26b82d..c0e74cdfde 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs @@ -41,7 +41,7 @@ namespace FancyZonesEditor } else if (xDelta > 0) { - xDelta = Math.Min(xDelta, c_workArea.Width - rect.Width - rect.X); + xDelta = Math.Min(xDelta, _settings.WorkArea.Width - rect.Width - rect.X); } if (yDelta < 0) @@ -50,7 +50,7 @@ namespace FancyZonesEditor } else if (yDelta > 0) { - yDelta = Math.Min(yDelta, c_workArea.Height - rect.Height - rect.Y); + yDelta = Math.Min(yDelta, _settings.WorkArea.Height - rect.Height - rect.Y); } rect.X += (int) xDelta; @@ -69,13 +69,13 @@ namespace FancyZonesEditor { int newWidth = rect.Width + (int) xDelta; - if (newWidth < 48) + if (newWidth < c_minZoneSize) { - newWidth = 48; + newWidth = c_minZoneSize; } - else if (newWidth > (c_workArea.Width - rect.X)) + else if (newWidth > (_settings.WorkArea.Width - rect.X)) { - newWidth = (int) c_workArea.Width - rect.X; + newWidth = (int) _settings.WorkArea.Width - rect.X; } MinWidth = rect.Width = newWidth; } @@ -84,13 +84,13 @@ namespace FancyZonesEditor { int newHeight = rect.Height + (int)yDelta; - if (newHeight < 48) + if (newHeight < c_minZoneSize) { - newHeight = 48; + newHeight = c_minZoneSize; } - else if (newHeight > (c_workArea.Height - rect.Y)) + else if (newHeight > (_settings.WorkArea.Height - rect.Y)) { - newHeight = (int)c_workArea.Height - rect.Y; + newHeight = (int)_settings.WorkArea.Height - rect.Y; } MinHeight = rect.Height = newHeight; } @@ -98,10 +98,7 @@ namespace FancyZonesEditor } private static int c_zIndex = 0; - - // TODO: multimon - // This needs to be the work area of the monitor we get launched on - private static Rect c_workArea = System.Windows.SystemParameters.WorkArea; + private static int c_minZoneSize = 48; protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { @@ -163,5 +160,7 @@ namespace FancyZonesEditor ((Panel)Parent).Children.Remove(this); Model.RemoveZoneAt(ZoneIndex); } + + private Settings _settings = ((App)Application.Current).ZoneSettings; } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs index e537e9b523..31d6690cf8 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs @@ -1,138 +1,135 @@ -using FancyZonesEditor.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.NetworkInformation; -using System.Security.RightsManagement; -using System.Text; -using System.Threading.Tasks; -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 -{ - /// - /// Interaction logic for Window1.xaml - /// - public partial class EditorOverlay : Window - { - public Int32Rect[] GetZoneRects() - { - // 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 - Panel previewPanel = null; - - if (_editor != null) - { - GridEditor gridEditor = _editor as GridEditor; - if (gridEditor != null) - { - previewPanel = gridEditor.PreviewPanel; - } - else - { - //CanvasEditor - previewPanel = ((CanvasEditor)_editor).Preview; - } - } - else - { - previewPanel = _layoutPreview.PreviewPanel; - } - - var count = previewPanel.Children.Count; - Int32Rect[] zones = new Int32Rect[count]; - - int i = 0; - foreach (FrameworkElement child in previewPanel.Children) - { - Point topLeft = child.TransformToAncestor(previewPanel).Transform(new Point()); - - var right = topLeft.X + child.ActualWidth; - var bottom = topLeft.Y + child.ActualHeight; - zones[i].X = (int)topLeft.X; - zones[i].Y = (int)topLeft.Y; - zones[i].Width = (int)child.ActualWidth; - zones[i].Height = (int)child.ActualHeight; - i++; - } - - return zones; - } - - public static EditorOverlay Current; - public EditorOverlay() - { - InitializeComponent(); - Current = this; +using FancyZonesEditor.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Security.RightsManagement; +using System.Text; +using System.Threading.Tasks; +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; - // TODO: multimon - // Need to set Left and Top to the correct monitor based on the - // foreground window passed in the command line arguments - Rect workArea = System.Windows.SystemParameters.WorkArea; - Left = workArea.Left; - Top = workArea.Top; - Width = workArea.Width; - Height = workArea.Height; +namespace FancyZonesEditor +{ + /// + /// Interaction logic for Window1.xaml + /// + public partial class EditorOverlay : Window + { + public Int32Rect[] GetZoneRects() + { + // 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 + Panel previewPanel = null; + + if (_editor != null) + { + GridEditor gridEditor = _editor as GridEditor; + if (gridEditor != null) + { + previewPanel = gridEditor.PreviewPanel; + } + else + { + //CanvasEditor + previewPanel = ((CanvasEditor)_editor).Preview; + } + } + else + { + previewPanel = _layoutPreview.PreviewPanel; + } + + var count = previewPanel.Children.Count; + Int32Rect[] zones = new Int32Rect[count]; + + int i = 0; + foreach (FrameworkElement child in previewPanel.Children) + { + Point topLeft = child.TransformToAncestor(previewPanel).Transform(new Point()); + + var right = topLeft.X + child.ActualWidth; + var bottom = topLeft.Y + child.ActualHeight; + zones[i].X = (int)topLeft.X; + zones[i].Y = (int)topLeft.Y; + zones[i].Width = (int)child.ActualWidth; + zones[i].Height = (int)child.ActualHeight; + i++; + } + + return zones; } - void onLoad(object sender, RoutedEventArgs e) - { - ShowLayoutPicker(); - } - - public void ShowLayoutPicker() - { - DataContext = null; - - _editor = null; - _layoutPreview = new LayoutPreview(); - _layoutPreview.IsActualSize = true; - _layoutPreview.Opacity = 0.5; - Content = _layoutPreview; - - MainWindow window = new MainWindow(); - window.Owner = this; - window.Show(); - } - - // 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; - } - - private Settings _settings = ((App)Application.Current).ZoneSettings; - private LayoutPreview _layoutPreview; - private UserControl _editor; - } -} + public static EditorOverlay Current; + public EditorOverlay() + { + InitializeComponent(); + Current = this; + + Left = _settings.WorkArea.Left; + Top = _settings.WorkArea.Top; + Width = _settings.WorkArea.Width; + Height = _settings.WorkArea.Height; + } + + void onLoad(object sender, RoutedEventArgs e) + { + ShowLayoutPicker(); + } + + public void ShowLayoutPicker() + { + DataContext = null; + + _editor = null; + _layoutPreview = new LayoutPreview(); + _layoutPreview.IsActualSize = true; + _layoutPreview.Opacity = 0.5; + Content = _layoutPreview; + + MainWindow window = new MainWindow(); + window.Owner = this; + window.ShowActivated = true; + window.Show(); + } + + // 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; + } + + private Settings _settings = ((App)Application.Current).ZoneSettings; + private LayoutPreview _layoutPreview; + private UserControl _editor; + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 6a612d958b..f86f47439d 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -155,7 +155,8 @@ namespace FancyZonesEditor.Models internal delegate int PersistZoneSet( [MarshalAs(UnmanagedType.LPWStr)] string activeKey, - [MarshalAs(UnmanagedType.LPWStr)] string key, + [MarshalAs(UnmanagedType.LPWStr)] string resolutionKey, + uint monitor, ushort layoutId, int zoneCount, [MarshalAs(UnmanagedType.LPArray)] int[] zoneArray); @@ -186,14 +187,12 @@ namespace FancyZonesEditor.Models // Scale all the zones to the DPI and then pack them up to be marshalled. int zoneCount = zones.Length; var zoneArray = new int[zoneCount * 4]; - var graphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero); - float dpi = graphics.DpiX / 96; for (int i = 0; i < zones.Length; i++) { - var left = (int)(zones[i].X * dpi); - var top = (int)(zones[i].Y * dpi); - var right = left + (int)(zones[i].Width * dpi); - var bottom = top + (int)(zones[i].Height * dpi); + var left = (int)(zones[i].X * Settings.Dpi); + var top = (int)(zones[i].Y * Settings.Dpi); + var right = left + (int)(zones[i].Width * Settings.Dpi); + var bottom = top + (int)(zones[i].Height * Settings.Dpi); var index = i * 4; zoneArray[index] = left; @@ -202,22 +201,8 @@ namespace FancyZonesEditor.Models zoneArray[index+3] = bottom; } - string[] args = Environment.GetCommandLineArgs(); - if (args.Length > 1) - { - // args[1] = registry key value of currently active ZoneSet - // args[2] = id of layout to load at startup - - string uniqueId = args[1]; - - // TODO: multimon - double height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; - double width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; - var key = width.ToString() + "_" + height.ToString(); - - var persistZoneSet = Marshal.GetDelegateForFunctionPointer(pfn); - persistZoneSet(uniqueId, key, _id, zoneCount, zoneArray); - } + var persistZoneSet = Marshal.GetDelegateForFunctionPointer(pfn); + persistZoneSet(Settings.UniqueKey, Settings.WorkAreaKey, Settings.Monitor, _id, zoneCount, zoneArray); } private static readonly string c_registryPath = Settings.RegistryPath + "\\Layouts"; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 0f10102924..0f19c51703 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -1,304 +1,375 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.ComponentModel; -using System.Collections; -using System.Collections.ObjectModel; -using FancyZonesEditor.Models; -using System.Windows.Documents; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Win32; - -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 Settings() - { - Rect workArea = System.Windows.SystemParameters.WorkArea; - - // Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid - _defaultModels = new List(5); - _focusModel = new CanvasLayoutModel("Focus", c_focusModelId, (int)workArea.Width, (int)workArea.Height); - _defaultModels.Add(_focusModel); - - _columnsModel = new GridLayoutModel("Columns", c_columnsModelId); - _columnsModel.Rows = 1; - _columnsModel.RowPercents = new int[1] { c_multiplier }; - _defaultModels.Add(_columnsModel); - - _rowsModel = new GridLayoutModel("Rows", c_rowsModelId); - _rowsModel.Columns = 1; - _rowsModel.ColumnPercents = new int[1] { c_multiplier }; - _defaultModels.Add(_rowsModel); - - _gridModel = new GridLayoutModel("Grid", c_gridModelId); - _defaultModels.Add(_gridModel); - - _priorityGridModel = new GridLayoutModel("Priority Grid", c_priorityGridModelId); - _defaultModels.Add(_priorityGridModel); - - _blankCustomModel = new CanvasLayoutModel("Create new custom", c_blankCustomModelId, (int)workArea.Width, (int)workArea.Height); - - _zoneCount = (int)Registry.GetValue(FullRegistryPath, "ZoneCount", 3); - _spacing = (int)Registry.GetValue(FullRegistryPath, "Spacing", 16); - _showSpacing = (int)Registry.GetValue(FullRegistryPath, "ShowSpacing", 1) == 1; - - UpdateLayoutModels(); - } - - // ZoneCount - number of zones selected in the picker window - public int ZoneCount - { - get { return _zoneCount; } - set - { - if (_zoneCount != value) - { - _zoneCount = value; - Registry.SetValue(FullRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord); - UpdateLayoutModels(); - FirePropertyChanged("ZoneCount"); - } - } - } - private int _zoneCount; - - // Spacing - how much space in between zones of the grid do you want - public int Spacing - { - get { return _spacing; } - set - { - if (_spacing != value) - { - _spacing = value; - Registry.SetValue(FullRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord); - FirePropertyChanged("Spacing"); - } - } - } - private int _spacing; - - // ShowSpacing - is the Spacing value used or ignored? - public bool ShowSpacing - { - get { return _showSpacing; } - set - { - if (_showSpacing != value) - { - _showSpacing = value; - Registry.SetValue(FullRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord); - FirePropertyChanged("ShowSpacing"); - } - } - } - private bool _showSpacing; - - // IsShiftKeyPressed - is the shift key currently being held down - public bool IsShiftKeyPressed - { - get { return _isShiftKeyPressed; } - set - { - if (_isShiftKeyPressed != value) - { - _isShiftKeyPressed = value; - FirePropertyChanged("IsShiftKeyPressed"); - } - } - } - private bool _isShiftKeyPressed; - - // IsCtrlKeyPressed - is the ctrl key currently being held down - public bool IsCtrlKeyPressed - { - get { return _isCtrlKeyPressed; } - set - { - if (_isCtrlKeyPressed != value) - { - _isCtrlKeyPressed = value; - FirePropertyChanged("IsCtrlKeyPressed"); - } - } - } - private bool _isCtrlKeyPressed; - - // UpdateLayoutModels - // Update the five default layouts based on the new ZoneCount - private void UpdateLayoutModels() - { - int previousZoneCount = _focusModel.Zones.Count; - - // Update the "Focus" Default Layout - _focusModel.Zones.Clear(); - - Int32Rect focusZoneRect = new Int32Rect((int)(_focusModel.ReferenceWidth * 0.1), (int)(_focusModel.ReferenceHeight * 0.1), (int)(_focusModel.ReferenceWidth * 0.6), (int)(_focusModel.ReferenceHeight * 0.6)); - int focusRectXIncrement = (ZoneCount <= 1) ? 0 : (int)(_focusModel.ReferenceWidth * 0.2) / (ZoneCount - 1); - int focusRectYIncrement = (ZoneCount <= 1) ? 0 : (int)(_focusModel.ReferenceHeight * 0.2) / (ZoneCount - 1); - - for (int i = 0; i < ZoneCount; i++) - { - _focusModel.Zones.Add(focusZoneRect); - focusZoneRect.X += focusRectXIncrement; - focusZoneRect.Y += focusRectYIncrement; - } - - // Update the "Rows" and "Columns" Default Layouts - // They can share their model, just transposed - _rowsModel.CellChildMap = new int[ZoneCount, 1]; - _columnsModel.CellChildMap = new int[1, ZoneCount]; - _rowsModel.Rows = _columnsModel.Columns = ZoneCount; - _rowsModel.RowPercents = _columnsModel.ColumnPercents = new int[ZoneCount]; - - for (int i = 0; i < ZoneCount; i++) - { - _rowsModel.CellChildMap[i, 0] = i; - _columnsModel.CellChildMap[0, i] = i; - _rowsModel.RowPercents[i] = c_multiplier / ZoneCount; // _columnsModel is sharing the same array - } - - // Update the "Grid" Default Layout - int rows = 1; - int cols = 1; - int mergeCount = 0; - while (ZoneCount / rows >= rows) - { - rows++; - } - rows--; - cols = ZoneCount / rows; - if (ZoneCount % rows == 0) - { - // even grid - } - else - { - cols++; - mergeCount = rows - (ZoneCount % rows); - } - _gridModel.Rows = rows; - _gridModel.Columns = cols; - _gridModel.RowPercents = new int[rows]; - _gridModel.ColumnPercents = new int[cols]; - _gridModel.CellChildMap = new int[rows, cols]; - - for (int row = 0; row < rows; row++) - { - _gridModel.RowPercents[row] = c_multiplier / rows; - } - - for (int col = 0; col < cols; col++) - { - _gridModel.ColumnPercents[col] = c_multiplier / cols; - } - - int index = 0; - for (int col = cols - 1; col >= 0; col--) - { - for (int row = rows - 1; row >= 0; row--) - { - _gridModel.CellChildMap[row, col] = index++; - if (index == ZoneCount) - { - index--; - } - - } - } - - // Update the "Priority Grid" Default Layout - if (ZoneCount <= s_priorityData.Length) - { - _priorityGridModel.Reload(s_priorityData[ZoneCount - 1]); - } - else - { - // same as grid; - _priorityGridModel.Rows = _gridModel.Rows; - _priorityGridModel.Columns = _gridModel.Columns; - _priorityGridModel.RowPercents = _gridModel.RowPercents; - _priorityGridModel.ColumnPercents = _gridModel.ColumnPercents; - _priorityGridModel.CellChildMap = _gridModel.CellChildMap; - } - } - - public IList DefaultModels { get { return _defaultModels; } } - public ObservableCollection CustomModels - { - get - { - if (_customModels == null) - { - _customModels = LayoutModel.LoadCustomModels(); - _customModels.Insert(0, _blankCustomModel); - } - return _customModels; - } - } - private ObservableCollection _customModels; - - public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones"; - public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath; - - public static bool IsPredefinedLayout(LayoutModel model) - { - return (model.Id >= c_lastPrefinedId); - } - - // implementation of INotifyProeprtyChanged - public event PropertyChangedEventHandler PropertyChanged; - - // FirePropertyChanged -- wrapper that calls INPC.PropertyChanged - protected virtual void FirePropertyChanged(string propertyName) - { - PropertyChangedEventHandler handler = PropertyChanged; - if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); - } - - // storage for Default Layout Models - private IList _defaultModels; - private CanvasLayoutModel _focusModel; - private GridLayoutModel _rowsModel; - private GridLayoutModel _columnsModel; - private GridLayoutModel _gridModel; - private GridLayoutModel _priorityGridModel; - private CanvasLayoutModel _blankCustomModel; - - private static readonly ushort c_focusModelId = 0xFFFF; - private static readonly ushort c_rowsModelId = 0xFFFE; - private static readonly ushort c_columnsModelId = 0xFFFD; - private static readonly ushort c_gridModelId = 0xFFFC; - private static readonly ushort c_priorityGridModelId = 0xFFFB; - private static readonly ushort c_blankCustomModelId = 0xFFFA; - private static readonly ushort c_lastPrefinedId = c_blankCustomModelId; - - // hard coded data for all the "Priority Grid" configurations that are unique to "Grid" - private static byte[][] s_priorityData = new byte[][] - { - new byte[] { 0, 0, 0, 0, 0, 1, 1, 39, 16, 39, 16, 0 }, - new byte[] { 0, 0, 0, 0, 0, 1, 2, 39, 16, 26, 11, 13, 5, 0, 1 }, - new byte[] { 0, 0, 0, 0, 0, 1, 3, 39, 16, 9, 196, 19, 136, 9, 196, 0, 1, 2 }, - new byte[] { 0, 0, 0, 0, 0, 2, 3, 19, 136, 19, 136, 9, 196, 19, 136, 9, 196, 0, 1, 2, 0, 1, 3 }, - new byte[] { 0, 0, 0, 0, 0, 2, 3, 19, 136, 19, 136, 9, 196, 19, 136, 9, 196, 0, 1, 2, 3, 1, 4 }, - new byte[] { 0, 0, 0, 0, 0, 3, 3, 13, 5, 13, 6, 13, 5, 9, 196, 19, 136, 9, 196, 0, 1, 2, 0, 1, 3, 4, 1, 5 }, - new byte[] { 0, 0, 0, 0, 0, 3, 3, 13, 5, 13, 6, 13, 5, 9, 196, 19, 136, 9, 196, 0, 1, 2, 3, 1, 4, 5, 1, 6 }, - new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 2, 5, 6, 1, 2, 7 }, - new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 2, 5, 6, 1, 7, 8 }, - new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 5, 6, 7, 1, 8, 9 }, - new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 5, 6, 7, 8, 9, 10 } - }; - - private const int c_multiplier = 10000; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; +using System.Collections; +using System.Collections.ObjectModel; +using FancyZonesEditor.Models; +using System.Windows.Documents; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Win32; + +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 Settings() + { + ParseCommandLineArgs(); + + // Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid + _defaultModels = new List(5); + _focusModel = new CanvasLayoutModel("Focus", c_focusModelId, (int)_workArea.Width, (int)_workArea.Height); + _defaultModels.Add(_focusModel); + + _columnsModel = new GridLayoutModel("Columns", c_columnsModelId); + _columnsModel.Rows = 1; + _columnsModel.RowPercents = new int[1] { c_multiplier }; + _defaultModels.Add(_columnsModel); + + _rowsModel = new GridLayoutModel("Rows", c_rowsModelId); + _rowsModel.Columns = 1; + _rowsModel.ColumnPercents = new int[1] { c_multiplier }; + _defaultModels.Add(_rowsModel); + + _gridModel = new GridLayoutModel("Grid", c_gridModelId); + _defaultModels.Add(_gridModel); + + _priorityGridModel = new GridLayoutModel("Priority Grid", c_priorityGridModelId); + _defaultModels.Add(_priorityGridModel); + + _blankCustomModel = new CanvasLayoutModel("Create new custom", c_blankCustomModelId, (int)_workArea.Width, (int)_workArea.Height); + + _zoneCount = (int)Registry.GetValue(_uniqueRegistryPath, "ZoneCount", 3); + _spacing = (int)Registry.GetValue(_uniqueRegistryPath, "Spacing", 16); + _showSpacing = (int)Registry.GetValue(_uniqueRegistryPath, "ShowSpacing", 1) == 1; + + UpdateLayoutModels(); + } + + // ZoneCount - number of zones selected in the picker window + public int ZoneCount + { + get { return _zoneCount; } + set + { + if (_zoneCount != value) + { + _zoneCount = value; + Registry.SetValue(_uniqueRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord); + UpdateLayoutModels(); + FirePropertyChanged("ZoneCount"); + } + } + } + private int _zoneCount; + + // Spacing - how much space in between zones of the grid do you want + public int Spacing + { + get { return _spacing; } + set + { + if (_spacing != value) + { + _spacing = value; + Registry.SetValue(_uniqueRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord); + FirePropertyChanged("Spacing"); + } + } + } + private int _spacing; + + // ShowSpacing - is the Spacing value used or ignored? + public bool ShowSpacing + { + get { return _showSpacing; } + set + { + if (_showSpacing != value) + { + _showSpacing = value; + Registry.SetValue(_uniqueRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord); + FirePropertyChanged("ShowSpacing"); + } + } + } + private bool _showSpacing; + + // IsShiftKeyPressed - is the shift key currently being held down + public bool IsShiftKeyPressed + { + get { return _isShiftKeyPressed; } + set + { + if (_isShiftKeyPressed != value) + { + _isShiftKeyPressed = value; + FirePropertyChanged("IsShiftKeyPressed"); + } + } + } + private bool _isShiftKeyPressed; + + // IsCtrlKeyPressed - is the ctrl key currently being held down + public bool IsCtrlKeyPressed + { + get { return _isCtrlKeyPressed; } + set + { + if (_isCtrlKeyPressed != value) + { + _isCtrlKeyPressed = value; + FirePropertyChanged("IsCtrlKeyPressed"); + } + } + } + private bool _isCtrlKeyPressed; + + public Rect WorkArea + { + get { return _workArea; } + } + private Rect _workArea; + + public static uint Monitor + { + get { return _monitor; } + } + private static uint _monitor; + + public static String UniqueKey + { + get { return _uniqueKey; } + } + private static String _uniqueKey; + private String _uniqueRegistryPath; + + public static String WorkAreaKey + { + get { return _workAreaKey; } + } + private static String _workAreaKey; + + public static float Dpi + { + get { return _dpi; } + } + private static float _dpi; + + // UpdateLayoutModels + // Update the five default layouts based on the new ZoneCount + private void UpdateLayoutModels() + { + int previousZoneCount = _focusModel.Zones.Count; + + // Update the "Focus" Default Layout + _focusModel.Zones.Clear(); + + Int32Rect focusZoneRect = new Int32Rect((int)(_focusModel.ReferenceWidth * 0.1), (int)(_focusModel.ReferenceHeight * 0.1), (int)(_focusModel.ReferenceWidth * 0.6), (int)(_focusModel.ReferenceHeight * 0.6)); + int focusRectXIncrement = (ZoneCount <= 1) ? 0 : (int)(_focusModel.ReferenceWidth * 0.2) / (ZoneCount - 1); + int focusRectYIncrement = (ZoneCount <= 1) ? 0 : (int)(_focusModel.ReferenceHeight * 0.2) / (ZoneCount - 1); + + for (int i = 0; i < ZoneCount; i++) + { + _focusModel.Zones.Add(focusZoneRect); + focusZoneRect.X += focusRectXIncrement; + focusZoneRect.Y += focusRectYIncrement; + } + + // Update the "Rows" and "Columns" Default Layouts + // They can share their model, just transposed + _rowsModel.CellChildMap = new int[ZoneCount, 1]; + _columnsModel.CellChildMap = new int[1, ZoneCount]; + _rowsModel.Rows = _columnsModel.Columns = ZoneCount; + _rowsModel.RowPercents = _columnsModel.ColumnPercents = new int[ZoneCount]; + + for (int i = 0; i < ZoneCount; i++) + { + _rowsModel.CellChildMap[i, 0] = i; + _columnsModel.CellChildMap[0, i] = i; + _rowsModel.RowPercents[i] = c_multiplier / ZoneCount; // _columnsModel is sharing the same array + } + + // Update the "Grid" Default Layout + int rows = 1; + int cols = 1; + int mergeCount = 0; + while (ZoneCount / rows >= rows) + { + rows++; + } + rows--; + cols = ZoneCount / rows; + if (ZoneCount % rows == 0) + { + // even grid + } + else + { + cols++; + mergeCount = rows - (ZoneCount % rows); + } + _gridModel.Rows = rows; + _gridModel.Columns = cols; + _gridModel.RowPercents = new int[rows]; + _gridModel.ColumnPercents = new int[cols]; + _gridModel.CellChildMap = new int[rows, cols]; + + for (int row = 0; row < rows; row++) + { + _gridModel.RowPercents[row] = c_multiplier / rows; + } + + for (int col = 0; col < cols; col++) + { + _gridModel.ColumnPercents[col] = c_multiplier / cols; + } + + int index = 0; + for (int col = cols - 1; col >= 0; col--) + { + for (int row = rows - 1; row >= 0; row--) + { + _gridModel.CellChildMap[row, col] = index++; + if (index == ZoneCount) + { + index--; + } + + } + } + + // Update the "Priority Grid" Default Layout + if (ZoneCount <= s_priorityData.Length) + { + _priorityGridModel.Reload(s_priorityData[ZoneCount - 1]); + } + else + { + // same as grid; + _priorityGridModel.Rows = _gridModel.Rows; + _priorityGridModel.Columns = _gridModel.Columns; + _priorityGridModel.RowPercents = _gridModel.RowPercents; + _priorityGridModel.ColumnPercents = _gridModel.ColumnPercents; + _priorityGridModel.CellChildMap = _gridModel.CellChildMap; + } + } + + private void ParseCommandLineArgs() + { + _workArea = System.Windows.SystemParameters.WorkArea; + _monitor = 0; + _uniqueRegistryPath = FullRegistryPath; + _uniqueKey = ""; + _dpi = 1; + + string[] args = Environment.GetCommandLineArgs(); + if (args.Length == 7) + { + // 1 = unique key for per-monitor settings + // 2 = layoutid used to generate current layout (used to pick the default layout to show) + // 3 = handle to monitor (passed back to engine to persist data) + // 4 = X_Y_Width_Height (where EditorOverlay shows up) + // 5 = resolution key (passed back to engine to persist data) + // 6 = monitor DPI (float) + + _uniqueKey = args[1]; + _uniqueRegistryPath += "\\" + _uniqueKey; + + var parsedLocation = args[4].Split('_'); + var x = int.Parse(parsedLocation[0]); + var y = int.Parse(parsedLocation[1]); + var width = int.Parse(parsedLocation[2]); + var height = int.Parse(parsedLocation[3]); + + _workAreaKey = args[5]; + _dpi = float.Parse(args[6]); + _workArea = new Rect(x, y, width, height); + + uint monitor = 0; + if (uint.TryParse(args[4], out monitor)) + { + _monitor = monitor; + } + } + } + + + public IList DefaultModels { get { return _defaultModels; } } + public ObservableCollection CustomModels + { + get + { + if (_customModels == null) + { + _customModels = LayoutModel.LoadCustomModels(); + _customModels.Insert(0, _blankCustomModel); + } + return _customModels; + } + } + private ObservableCollection _customModels; + + public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones"; + public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath; + + public static bool IsPredefinedLayout(LayoutModel model) + { + return (model.Id >= c_lastPrefinedId); + } + + // implementation of INotifyProeprtyChanged + public event PropertyChangedEventHandler PropertyChanged; + + // FirePropertyChanged -- wrapper that calls INPC.PropertyChanged + protected virtual void FirePropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } + + // storage for Default Layout Models + private IList _defaultModels; + private CanvasLayoutModel _focusModel; + private GridLayoutModel _rowsModel; + private GridLayoutModel _columnsModel; + private GridLayoutModel _gridModel; + private GridLayoutModel _priorityGridModel; + private CanvasLayoutModel _blankCustomModel; + + private static readonly ushort c_focusModelId = 0xFFFF; + private static readonly ushort c_rowsModelId = 0xFFFE; + private static readonly ushort c_columnsModelId = 0xFFFD; + private static readonly ushort c_gridModelId = 0xFFFC; + private static readonly ushort c_priorityGridModelId = 0xFFFB; + private static readonly ushort c_blankCustomModelId = 0xFFFA; + private static readonly ushort c_lastPrefinedId = c_blankCustomModelId; + + // hard coded data for all the "Priority Grid" configurations that are unique to "Grid" + private static byte[][] s_priorityData = new byte[][] + { + new byte[] { 0, 0, 0, 0, 0, 1, 1, 39, 16, 39, 16, 0 }, + new byte[] { 0, 0, 0, 0, 0, 1, 2, 39, 16, 26, 11, 13, 5, 0, 1 }, + new byte[] { 0, 0, 0, 0, 0, 1, 3, 39, 16, 9, 196, 19, 136, 9, 196, 0, 1, 2 }, + new byte[] { 0, 0, 0, 0, 0, 2, 3, 19, 136, 19, 136, 9, 196, 19, 136, 9, 196, 0, 1, 2, 0, 1, 3 }, + new byte[] { 0, 0, 0, 0, 0, 2, 3, 19, 136, 19, 136, 9, 196, 19, 136, 9, 196, 0, 1, 2, 3, 1, 4 }, + new byte[] { 0, 0, 0, 0, 0, 3, 3, 13, 5, 13, 6, 13, 5, 9, 196, 19, 136, 9, 196, 0, 1, 2, 0, 1, 3, 4, 1, 5 }, + new byte[] { 0, 0, 0, 0, 0, 3, 3, 13, 5, 13, 6, 13, 5, 9, 196, 19, 136, 9, 196, 0, 1, 2, 3, 1, 4, 5, 1, 6 }, + new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 2, 5, 6, 1, 2, 7 }, + new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 2, 5, 6, 1, 7, 8 }, + new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 5, 6, 7, 1, 8, 9 }, + new byte[] { 0, 0, 0, 0, 0, 3, 4, 13, 5, 13, 6, 13, 5, 9, 196, 9, 196, 9, 196, 9, 196, 0, 1, 2, 3, 4, 1, 5, 6, 7, 8, 9, 10 } + }; + + private const int c_multiplier = 10000; + } +} diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 3b7f999d73..3ad01a7990 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "common/dpi_aware.h" struct FancyZones : public winrt::implements { @@ -237,18 +238,43 @@ void FancyZones::ToggleEditor() noexcept m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); } - // TODO: multimon support - // Pass in args so that the editor shows up on the correct monitor - // This can be an HWND, HMONITOR, or the X/Y/Width/Height of the monitor's work area, (whichever works best). - if (const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY)) + const HWND foregroundWindow = GetForegroundWindow(); + if (const HMONITOR monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTOPRIMARY)) { std::shared_lock readLock(m_lock); auto iter = m_zoneWindowMap.find(monitor); if (iter != m_zoneWindowMap.end()) { - // Pass command line args to the editor to tell it which layout it should pick by default - auto activeZoneSet = iter->second->ActiveZoneSet(); - std::wstring params = iter->second->UniqueId() + L" " + std::to_wstring(activeZoneSet->LayoutId()); + UINT dpi_x = 96; + UINT dpi_y = 96; + DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y); + + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(monitor, &mi); + + // X/Y need to start in unscaled screen coordinates to get to the proper top/left of the monitor + // From there, we need to scale the difference between the monitor and workarea rects to get the + // appropriate offset where the overlay should appear. + // This covers the cases where the taskbar is not at the bottom of the screen. + const auto x = mi.rcMonitor.left + MulDiv(mi.rcWork.left - mi.rcMonitor.left, 96, dpi_x); + const auto y = mi.rcMonitor.top + MulDiv(mi.rcWork.top - mi.rcMonitor.top, 96, dpi_y); + + // Location that the editor should occupy, scaled by DPI + std::wstring editorLocation = + std::to_wstring(x) + L"_" + + std::to_wstring(y) + L"_" + + std::to_wstring(MulDiv(mi.rcWork.right - mi.rcWork.left, 96, dpi_x)) + L"_" + + std::to_wstring(MulDiv(mi.rcWork.bottom - mi.rcWork.top, 96, dpi_y)); + + const std::wstring params = + iter->second->UniqueId() + L" " + + std::to_wstring(iter->second->ActiveZoneSet()->LayoutId()) + L" " + + std::to_wstring(reinterpret_cast(monitor)) + L" " + + editorLocation + L" " + + iter->second->WorkAreaKey() + L" " + + std::to_wstring(static_cast(dpi_x) / 96.0f); + SHELLEXECUTEINFO sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; sei.lpFile = L"modules\\FancyZonesEditor.exe"; diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 8b2aaf76b6..d7b1666c7e 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -18,6 +18,7 @@ public: IFACEMETHODIMP_(void) CycleActiveZoneSet(DWORD vkCode) noexcept; IFACEMETHODIMP_(std::wstring) DeviceId() noexcept { return { m_deviceId.get() }; } IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } + IFACEMETHODIMP_(std::wstring) WorkAreaKey() noexcept { return { m_workArea }; } IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 7979ee9216..566a520ee5 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -16,6 +16,7 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow IFACEMETHOD_(void, SaveWindowProcessToZoneIndex)(HWND window) = 0; IFACEMETHOD_(std::wstring, DeviceId)() = 0; IFACEMETHOD_(std::wstring, UniqueId)() = 0; + IFACEMETHOD_(std::wstring, WorkAreaKey)() = 0; IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0; };