Skip to content

Commit

Permalink
[FancyZones] Feature to create zone layouts spanning multiple monitors (
Browse files Browse the repository at this point in the history
#5289)

* Added the setting

* Added a property to Editor Settings

This will be used later

* Fixed a bug in the previous commit

* Simplified a method

* Added snapping points to the editor

* Simplified a method in ZoneSet

* Updated ZoneSet testcases

* Add a method to FancyZones / ZoneWindowHost

* Almost works

* The editor now launches, but FZ does not understand the results

* Refactored some code

* Snapping to a zone by dragging seems to work

* Hotkeys seem to work

* Refresh the work area handler after changing settings

* Fixed zones not snapping to monitor edges when moved

* Remove unused method in FancyZones.h

* Fixed an issue with DPI awareness

* Renamed setting to spanZonesAcrossMonitors

* Renamed a function

* Fixed a bug with the magnetic effect

* Fix restoring window positions on layout changes
  • Loading branch information
ivan100sic committed Aug 7, 2020
1 parent 26bf05d commit 8f98866
Show file tree
Hide file tree
Showing 23 changed files with 466 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public FZConfigProperties()
FancyzonesRestoreSize = new BoolProperty();
UseCursorposEditorStartupscreen = new BoolProperty(ConfigDefaults.DefaultUseCursorposEditorStartupscreen);
FancyzonesShowOnAllMonitors = new BoolProperty();
FancyzonesSpanZonesAcrossMonitors = new BoolProperty();
FancyzonesZoneHighlightColor = new StringProperty(ConfigDefaults.DefaultFancyZonesZoneHighlightColor);
FancyzonesHighlightOpacity = new IntProperty(50);
FancyzonesEditorHotkey = new KeyboardKeysProperty(DefaultHotkeyValue);
Expand Down Expand Up @@ -66,6 +67,9 @@ public FZConfigProperties()
[JsonPropertyName("fancyzones_show_on_all_monitors")]
public BoolProperty FancyzonesShowOnAllMonitors { get; set; }

[JsonPropertyName("fancyzones_span_zones_across_monitors")]
public BoolProperty FancyzonesSpanZonesAcrossMonitors { get; set; }

[JsonPropertyName("fancyzones_makeDraggedWindowTransparent")]
public BoolProperty FancyzonesMakeDraggedWindowTransparent { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@
<data name="FileExplorerPreview_ToggleSwitch_Preview_SVG.Header" xml:space="preserve">
<value>Enable SVG (.svg) preview</value>
</data>
<data name="FileExplorerPreview_ToggleSwitch_SVG_Thumbnail.Header" xml:space="preserve">
<data name="FileExplorerPreview_ToggleSwitch_SVG_Thumbnail.Header" xml:space="preserve">
<value>Enable SVG (.svg) thumbnails</value>
</data>
<data name="FileExplorerPreview_Description.Text" xml:space="preserve">
Expand Down Expand Up @@ -642,4 +642,7 @@
<data name="About_PowerToys.Text" xml:space="preserve">
<value>Microsoft PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity</value>
</data>
<data name="FancyZones_SpanZonesAcrossMonitorsCheckBoxControl.Content" xml:space="preserve">
<value>Allow zones to span across monitors</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ public FancyZonesViewModel()
_restoreSize = Settings.Properties.FancyzonesRestoreSize.Value;
_useCursorPosEditorStartupScreen = Settings.Properties.UseCursorposEditorStartupscreen.Value;
_showOnAllMonitors = Settings.Properties.FancyzonesShowOnAllMonitors.Value;
_spanZonesAcrossMonitors = Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value;
_makeDraggedWindowTransparent = Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value;
_highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value;
_excludedApps = Settings.Properties.FancyzonesExcludedApps.Value;
EditorHotkey = Settings.Properties.FancyzonesEditorHotkey.Value;

string inactiveColor = Settings.Properties.FancyzonesInActiveColor.Value;
_zoneInActiveColor = inactiveColor != string.Empty ? inactiveColor.ToColor() : "#F5FCFF".ToColor();

Expand Down Expand Up @@ -82,6 +82,7 @@ public FancyZonesViewModel()
private bool _zoneSetChangeMoveWindows;
private bool _appLastZoneMoveWindows;
private bool _openWindowOnActiveMonitor;
private bool _spanZonesAcrossMonitors;
private bool _restoreSize;
private bool _useCursorPosEditorStartupScreen;
private bool _showOnAllMonitors;
Expand Down Expand Up @@ -314,6 +315,24 @@ public bool ShowOnAllMonitors
}
}

public bool SpanZonesAcrossMonitors
{
get
{
return _spanZonesAcrossMonitors;
}

set
{
if (value != _spanZonesAcrossMonitors)
{
_spanZonesAcrossMonitors = value;
Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value = value;
RaisePropertyChanged();
}
}
}

public bool MakeDraggedWindowsTransparent
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"/>

<CheckBox x:Uid="FancyZones_SpanZonesAcrossMonitorsCheckBoxControl"
IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.SpanZonesAcrossMonitors}"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"/>

<CheckBox x:Uid="FancyZones_MakeDraggedWindowTransparentCheckBoxControl"
IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.MakeDraggedWindowsTransparent}"
Margin="{StaticResource SmallTopMargin}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ public void ShowOnAllMonitors_ShouldSetValue2True_WhenSuccessful()
viewModel.ShowOnAllMonitors = true;
}

[TestMethod]
public void SpanZonesAcrossMonitors_ShouldSetValue2True_WhenSuccessful()
{
// arrange
FancyZonesViewModel viewModel = new FancyZonesViewModel();
Assert.IsFalse(viewModel.SpanZonesAcrossMonitors); // check if value was initialized to false.

// Assert
ShellPage.DefaultSndMSGCallback = msg =>
{
FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize<FancyZonesSettingsIPCMessage>(msg);
Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesSpanZonesAcrossMonitors.Value);
};

// act
viewModel.SpanZonesAcrossMonitors = true;
}

[TestMethod]
public void ZoneHighlightColor_ShouldSetColorValue2White_WhenSuccessful()
{
Expand Down
54 changes: 35 additions & 19 deletions src/modules/fancyzones/editor/FancyZonesEditor/CanvasZone.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ private abstract class SnappyHelperBase
/// <param name="zoneIndex">The index of the zone to track</param>
/// <param name="isX"> Whether this is the X or Y SnappyHelper</param>
/// <param name="mode"> One of the three modes of operation (for example: tracking left/right/both edges)</param>
/// <param name="screenAxisOrigin"> The origin (left/top) of the screen in this (X or Y) dimension</param>
/// <param name="screenAxisSize"> The size of the screen in this (X or Y) dimension</param>
public SnappyHelperBase(IList<Int32Rect> zones, int zoneIndex, bool isX, ResizeMode mode, int screenAxisSize)
public SnappyHelperBase(IList<Int32Rect> zones, int zoneIndex, bool isX, ResizeMode mode, int screenAxisOrigin, int screenAxisSize)
{
int zonePosition = isX ? zones[zoneIndex].X : zones[zoneIndex].Y;
int zoneAxisSize = isX ? zones[zoneIndex].Width : zones[zoneIndex].Height;
Expand All @@ -83,6 +84,19 @@ public SnappyHelperBase(IList<Int32Rect> zones, int zoneIndex, bool isX, ResizeM
}
}

foreach (Rect singleMonitor in Settings.UsedWorkAreas)
{
int monitorPositionLow = (int)(isX ? singleMonitor.Left : singleMonitor.Top);
int monitorPositionHigh = (int)(isX ? singleMonitor.Right : singleMonitor.Bottom);
keyPositions.Add(monitorPositionLow - screenAxisOrigin);
keyPositions.Add(monitorPositionHigh - screenAxisOrigin);
if (mode == ResizeMode.BothEdges)
{
keyPositions.Add(monitorPositionLow - screenAxisOrigin - zoneAxisSize);
keyPositions.Add(monitorPositionHigh - screenAxisOrigin - zoneAxisSize);
}
}

// Remove duplicates and sort
keyPositions.Sort();
Snaps = new List<int>();
Expand Down Expand Up @@ -143,8 +157,8 @@ private int MagnetZoneMaxSize
get => (int)(0.08 * ScreenW);
}

public SnappyHelperMagnetic(IList<Int32Rect> zones, int zoneIndex, bool isX, ResizeMode mode, int screenAxisSize)
: base(zones, zoneIndex, isX, mode, screenAxisSize)
public SnappyHelperMagnetic(IList<Int32Rect> zones, int zoneIndex, bool isX, ResizeMode mode, int screenAxisOrigin, int screenAxisSize)
: base(zones, zoneIndex, isX, mode, screenAxisOrigin, screenAxisSize)
{
freePosition = Position;
magnetZoneSizes = new List<int>();
Expand Down Expand Up @@ -197,9 +211,11 @@ public override void Move(int delta)
private SnappyHelperBase snappyX;
private SnappyHelperBase snappyY;

private SnappyHelperBase NewDefaultSnappyHelper(bool isX, ResizeMode mode, int screenAxisSize)
private SnappyHelperBase NewDefaultSnappyHelper(bool isX, ResizeMode mode)
{
return new SnappyHelperMagnetic(Model.Zones, ZoneIndex, isX, mode, screenAxisSize);
int screenAxisOrigin = (int)(isX ? Settings.WorkArea.Left : Settings.WorkArea.Top);
int screenAxisSize = (int)(isX ? Settings.WorkArea.Width : Settings.WorkArea.Height);
return new SnappyHelperMagnetic(Model.Zones, ZoneIndex, isX, mode, screenAxisOrigin, screenAxisSize);
}

private void UpdateFromSnappyHelpers()
Expand Down Expand Up @@ -287,8 +303,8 @@ private void OnClose(object sender, RoutedEventArgs e)
// Corner dragging
private void Caption_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BothEdges, (int)Settings.WorkArea.Width);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BothEdges, (int)Settings.WorkArea.Height);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BothEdges);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BothEdges);
}

public CanvasLayoutModel Model { get => model; set => model = value; }
Expand All @@ -297,50 +313,50 @@ private void Caption_DragStarted(object sender, System.Windows.Controls.Primitiv

private void NWResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge, (int)Settings.WorkArea.Width);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge, (int)Settings.WorkArea.Height);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge);
}

private void NEResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge, (int)Settings.WorkArea.Width);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge, (int)Settings.WorkArea.Height);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge);
}

private void SWResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge, (int)Settings.WorkArea.Width);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge, (int)Settings.WorkArea.Height);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge);
}

private void SEResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge, (int)Settings.WorkArea.Width);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge, (int)Settings.WorkArea.Height);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge);
}

// Edge dragging
private void NResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = null;
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge, (int)Settings.WorkArea.Height);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.BottomEdge);
}

private void SResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = null;
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge, (int)Settings.WorkArea.Height);
snappyY = NewDefaultSnappyHelper(false, ResizeMode.TopEdge);
}

private void WResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge, (int)Settings.WorkArea.Width);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.BottomEdge);
snappyY = null;
}

private void EResize_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge, (int)Settings.WorkArea.Width);
snappyX = NewDefaultSnappyHelper(true, ResizeMode.TopEdge);
snappyY = null;
}
}
Expand Down
37 changes: 27 additions & 10 deletions src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ public bool IsCtrlKeyPressed

public static Rect WorkArea { get; private set; }

public static List<Rect> UsedWorkAreas { get; private set; }

public static string UniqueKey { get; private set; }

public static string ActiveZoneSetUUid { get; private set; }
Expand Down Expand Up @@ -453,6 +455,7 @@ private void ParseDeviceInfoData(ParseDeviceMode mode = ParseDeviceMode.Prod)
private void ParseCommandLineArgs()
{
WorkArea = SystemParameters.WorkArea;
UsedWorkAreas = new List<Rect> { WorkArea };

string[] args = Environment.GetCommandLineArgs();

Expand All @@ -470,19 +473,33 @@ private void ParseCommandLineArgs()
}
else if (args.Length == 3)
{
var parsedLocation = args[(int)CmdArgs.WorkAreaSize].Split('_');
if (parsedLocation.Length != 4)
UsedWorkAreas.Clear();
foreach (var singleMonitorString in args[(int)CmdArgs.WorkAreaSize].Split('/'))
{
MessageBox.Show(ErrorInvalidArgs, ErrorMessageBoxTitle);
((App)Application.Current).Shutdown();
}
var parsedLocation = singleMonitorString.Split('_');
if (parsedLocation.Length != 4)
{
MessageBox.Show(ErrorInvalidArgs, ErrorMessageBoxTitle);
((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]);
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]);

WorkArea = new Rect(x, y, width, 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();
Expand Down

0 comments on commit 8f98866

Please sign in to comment.