mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
[FancyZonesEditor]: Grid Editor keyboard control (#12969)
- Ctrl+Tab to switch between zones and layout overlay window - Tab to focus between grid zones and resizers - While resizer is focused: arrows to move it; Del to remove it - While zone is focused: (Shift)+S to split it horizontally/vertically
This commit is contained in:
@@ -12,6 +12,7 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
using FancyZonesEditor.Utils;
|
using FancyZonesEditor.Utils;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Common.UI;
|
using Microsoft.PowerToys.Common.UI;
|
||||||
@@ -178,6 +179,11 @@ namespace FancyZonesEditor
|
|||||||
{
|
{
|
||||||
MainWindowSettings.IsShiftKeyPressed = true;
|
MainWindowSettings.IsShiftKeyPressed = true;
|
||||||
}
|
}
|
||||||
|
else if (e.Key == Key.Tab && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
App.Overlay.FocusEditor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShowExceptionMessageBox(string message, Exception exception = null)
|
public static void ShowExceptionMessageBox(string message, Exception exception = null)
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using FancyZonesEditor.Models;
|
using FancyZonesEditor.Models;
|
||||||
|
|
||||||
@@ -38,9 +40,20 @@ namespace FancyZonesEditor
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Loaded += GridEditor_Loaded;
|
Loaded += GridEditor_Loaded;
|
||||||
Unloaded += GridEditor_Unloaded;
|
Unloaded += GridEditor_Unloaded;
|
||||||
|
KeyDown += GridEditor_KeyDown;
|
||||||
|
KeyUp += GridEditor_KeyUp;
|
||||||
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
|
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FocusZone()
|
||||||
|
{
|
||||||
|
if (Preview.Children.Count > 0)
|
||||||
|
{
|
||||||
|
var zone = Preview.Children[0] as GridZone;
|
||||||
|
zone.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void GridEditor_Loaded(object sender, RoutedEventArgs e)
|
private void GridEditor_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
|
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
|
||||||
@@ -58,6 +71,134 @@ namespace FancyZonesEditor
|
|||||||
SetupUI();
|
SetupUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleResizerKeyDown(GridResizer resizer, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
DragDeltaEventArgs args = null;
|
||||||
|
if (resizer.Orientation == Orientation.Horizontal)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Up)
|
||||||
|
{
|
||||||
|
args = new DragDeltaEventArgs(0, -1);
|
||||||
|
}
|
||||||
|
else if (e.Key == Key.Down)
|
||||||
|
{
|
||||||
|
args = new DragDeltaEventArgs(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Left)
|
||||||
|
{
|
||||||
|
args = new DragDeltaEventArgs(-1, 0);
|
||||||
|
}
|
||||||
|
else if (e.Key == Key.Right)
|
||||||
|
{
|
||||||
|
args = new DragDeltaEventArgs(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args != null)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Resizer_DragDelta(resizer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Key == Key.Delete)
|
||||||
|
{
|
||||||
|
int resizerIndex = AdornerLayer.Children.IndexOf(resizer);
|
||||||
|
var resizerData = _data.Resizers[resizerIndex];
|
||||||
|
|
||||||
|
var indices = new List<int>(resizerData.PositiveSideIndices);
|
||||||
|
indices.AddRange(resizerData.NegativeSideIndices);
|
||||||
|
_data.DoMerge(indices);
|
||||||
|
SetupUI();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleResizerKeyUp(GridResizer resizer, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (resizer.Orientation == Orientation.Horizontal)
|
||||||
|
{
|
||||||
|
e.Handled = e.Key == Key.Up || e.Key == Key.Down;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
e.Handled = e.Key == Key.Left || e.Key == Key.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Handled)
|
||||||
|
{
|
||||||
|
int resizerIndex = AdornerLayer.Children.IndexOf(resizer);
|
||||||
|
Resizer_DragCompleted(resizer, null);
|
||||||
|
Debug.Assert(AdornerLayer.Children.Count > resizerIndex, "Resizer index out of range");
|
||||||
|
Keyboard.Focus(AdornerLayer.Children[resizerIndex]);
|
||||||
|
_dragY = _dragX = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGridZoneKeyUp(GridZone gridZone, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key != Key.S)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation orient = Orientation.Horizontal;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
int zoneIndex = Preview.Children.IndexOf(gridZone);
|
||||||
|
var zone = _data.Zones[zoneIndex];
|
||||||
|
Debug.Assert(Preview.Children.Count > zoneIndex, "Zone index out of range");
|
||||||
|
|
||||||
|
if (((App)Application.Current).MainWindowSettings.IsShiftKeyPressed)
|
||||||
|
{
|
||||||
|
orient = Orientation.Vertical;
|
||||||
|
offset = gridZone.SnapAtHalfX();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
offset = gridZone.SnapAtHalfY();
|
||||||
|
}
|
||||||
|
|
||||||
|
gridZone.DoSplit(orient, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GridEditor_KeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Tab && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
App.Overlay.FocusEditorWindow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resizer = Keyboard.FocusedElement as GridResizer;
|
||||||
|
if (resizer != null)
|
||||||
|
{
|
||||||
|
HandleResizerKeyDown(resizer, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GridEditor_KeyUp(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
var resizer = Keyboard.FocusedElement as GridResizer;
|
||||||
|
if (resizer != null)
|
||||||
|
{
|
||||||
|
HandleResizerKeyUp(resizer, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gridZone = Keyboard.FocusedElement as GridZone;
|
||||||
|
if (gridZone != null)
|
||||||
|
{
|
||||||
|
HandleGridZoneKeyUp(gridZone, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void GridEditor_Unloaded(object sender, RoutedEventArgs e)
|
private void GridEditor_Unloaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
((App)Application.Current).MainWindowSettings.PropertyChanged -= ZoneSettings_PropertyChanged;
|
((App)Application.Current).MainWindowSettings.PropertyChanged -= ZoneSettings_PropertyChanged;
|
||||||
@@ -267,7 +408,7 @@ namespace FancyZonesEditor
|
|||||||
delta = Convert.ToInt32(_dragY / actualSize.Height * GridData.Multiplier);
|
delta = Convert.ToInt32(_dragY / actualSize.Height * GridData.Multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_data.CanDrag(resizerIndex, delta))
|
if (resizerIndex != -1 && _data.CanDrag(resizerIndex, delta))
|
||||||
{
|
{
|
||||||
// Just update the UI, don't tell _data
|
// Just update the UI, don't tell _data
|
||||||
if (resizer.Orientation == Orientation.Vertical)
|
if (resizer.Orientation == Orientation.Vertical)
|
||||||
@@ -328,6 +469,12 @@ namespace FancyZonesEditor
|
|||||||
{
|
{
|
||||||
GridResizer resizer = (GridResizer)sender;
|
GridResizer resizer = (GridResizer)sender;
|
||||||
int resizerIndex = AdornerLayer.Children.IndexOf(resizer);
|
int resizerIndex = AdornerLayer.Children.IndexOf(resizer);
|
||||||
|
if (resizerIndex == -1)
|
||||||
|
{
|
||||||
|
// Resizer was removed during drag
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Size actualSize = WorkAreaSize();
|
Size actualSize = WorkAreaSize();
|
||||||
|
|
||||||
double pixelDelta = resizer.Orientation == Orientation.Vertical ?
|
double pixelDelta = resizer.Orientation == Orientation.Vertical ?
|
||||||
|
|||||||
@@ -53,6 +53,14 @@
|
|||||||
Text="{x:Static props:Resources.MergeName}" />
|
Text="{x:Static props:Resources.MergeName}" />
|
||||||
<Run Text="{x:Static props:Resources.MergeDescription}" />
|
<Run Text="{x:Static props:Resources.MergeDescription}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<Run
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="{x:Static props:Resources.KeyboardControlsName}" />
|
||||||
|
<Run Text="{x:Static props:Resources.KeyboardControlsDescription}" />
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid Margin="0,24,0,-4">
|
<Grid Margin="0,24,0,-4">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:FancyZonesEditor"
|
xmlns:local="clr-namespace:FancyZonesEditor"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
Focusable="True"
|
||||||
d:DesignHeight="300" d:DesignWidth="300">
|
d:DesignHeight="300" d:DesignWidth="300">
|
||||||
<Thumb.Template>
|
<Thumb.Template>
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
@@ -35,6 +36,10 @@
|
|||||||
TargetName="Body"
|
TargetName="Body"
|
||||||
Value="{DynamicResource SystemAccentColorLight1Brush}"/>
|
Value="{DynamicResource SystemAccentColorLight1Brush}"/>
|
||||||
</Trigger>
|
</Trigger>
|
||||||
|
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="Body"
|
||||||
|
Value="{DynamicResource SystemAccentColorLight3Brush}" />
|
||||||
|
</Trigger>
|
||||||
</ControlTemplate.Triggers>
|
</ControlTemplate.Triggers>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Thumb.Template>
|
</Thumb.Template>
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
BorderBrush="{DynamicResource SystemControlBackgroundAccentBrush}"
|
BorderBrush="{DynamicResource SystemControlBackgroundAccentBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Opacity="1"
|
Opacity="1"
|
||||||
|
Focusable="True"
|
||||||
|
IsTabStop="True"
|
||||||
ui:ControlHelper.CornerRadius="4"
|
ui:ControlHelper.CornerRadius="4"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace FancyZonesEditor
|
|||||||
private const string GridZoneBackgroundBrushID = "GridZoneBackgroundBrush";
|
private const string GridZoneBackgroundBrushID = "GridZoneBackgroundBrush";
|
||||||
private const string SecondaryForegroundBrushID = "SecondaryForegroundBrush";
|
private const string SecondaryForegroundBrushID = "SecondaryForegroundBrush";
|
||||||
private const string AccentColorBrushID = "SystemControlBackgroundAccentBrush";
|
private const string AccentColorBrushID = "SystemControlBackgroundAccentBrush";
|
||||||
|
private const string CanvasCanvasZoneBorderBrushID = "CanvasCanvasZoneBorderBrush";
|
||||||
|
|
||||||
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(ObjectDependencyID, typeof(bool), typeof(GridZone), new PropertyMetadata(false, OnSelectionChanged));
|
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(ObjectDependencyID, typeof(bool), typeof(GridZone), new PropertyMetadata(false, OnSelectionChanged));
|
||||||
|
|
||||||
@@ -75,12 +76,39 @@ namespace FancyZonesEditor
|
|||||||
|
|
||||||
SizeChanged += GridZone_SizeChanged;
|
SizeChanged += GridZone_SizeChanged;
|
||||||
|
|
||||||
|
GotKeyboardFocus += GridZone_GotKeyboardFocus;
|
||||||
|
LostKeyboardFocus += GridZone_LostKeyboardFocus;
|
||||||
|
|
||||||
_snapX = snapX;
|
_snapX = snapX;
|
||||||
_snapY = snapY;
|
_snapY = snapY;
|
||||||
_canSplit = canSplit;
|
_canSplit = canSplit;
|
||||||
_zone = zone;
|
_zone = zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int SnapAtHalfX()
|
||||||
|
{
|
||||||
|
var half = (_zone.Right - _zone.Left) / 2;
|
||||||
|
var pixelX = _snapX.DataToPixelWithoutSnapping(_zone.Left + half);
|
||||||
|
return _snapX.PixelToDataWithSnapping(pixelX, _zone.Left, _zone.Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SnapAtHalfY()
|
||||||
|
{
|
||||||
|
var half = (_zone.Bottom - _zone.Top) / 2;
|
||||||
|
var pixelY = _snapY.DataToPixelWithoutSnapping(_zone.Top + half);
|
||||||
|
return _snapY.PixelToDataWithSnapping(pixelY, _zone.Top, _zone.Bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GridZone_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Opacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GridZone_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Opacity = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
private void GridZone_SizeChanged(object sender, SizeChangedEventArgs e)
|
private void GridZone_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
{
|
{
|
||||||
// using current culture as this is end user facing
|
// using current culture as this is end user facing
|
||||||
@@ -241,7 +269,7 @@ namespace FancyZonesEditor
|
|||||||
MergeComplete?.Invoke(this, e);
|
MergeComplete?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoSplit(Orientation orientation, int offset)
|
public void DoSplit(Orientation orientation, int offset)
|
||||||
{
|
{
|
||||||
Split?.Invoke(this, new SplitEventArgs(orientation, offset));
|
Split?.Invoke(this, new SplitEventArgs(orientation, offset));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,10 +253,19 @@ namespace FancyZonesEditor
|
|||||||
|
|
||||||
public void FocusEditor()
|
public void FocusEditor()
|
||||||
{
|
{
|
||||||
if (_editorLayout != null && _editorLayout is CanvasEditor canvasEditor)
|
if (_editorLayout == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_editorLayout is CanvasEditor canvasEditor)
|
||||||
{
|
{
|
||||||
canvasEditor.FocusZone();
|
canvasEditor.FocusZone();
|
||||||
}
|
}
|
||||||
|
else if (_editorLayout is GridEditor gridEditor)
|
||||||
|
{
|
||||||
|
gridEditor.FocusZone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FocusEditorWindow()
|
public void FocusEditorWindow()
|
||||||
|
|||||||
@@ -447,6 +447,29 @@ namespace FancyZonesEditor.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to
|
||||||
|
/// - [Shift]+S to split currently focused zone.
|
||||||
|
/// - Ctrl+Tab to focus zones/resizers.
|
||||||
|
/// - Tab to cycle zones and resizers.
|
||||||
|
/// - Delete to remove the focused resizer.
|
||||||
|
/// - Arrows to move the focused resizer..
|
||||||
|
/// </summary>
|
||||||
|
public static string KeyboardControlsDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("KeyboardControlsDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Keyboard Navigation:.
|
||||||
|
/// </summary>
|
||||||
|
public static string KeyboardControlsName {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("KeyboardControlsName", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Create layouts that have overlapping zones.
|
/// Looks up a localized string similar to Create layouts that have overlapping zones.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -336,6 +336,17 @@
|
|||||||
<value>Merge/Delete:</value>
|
<value>Merge/Delete:</value>
|
||||||
<comment>Title for concept behind Merging two zones together or removing an zone</comment>
|
<comment>Title for concept behind Merging two zones together or removing an zone</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="KeyboardControlsName" xml:space="preserve">
|
||||||
|
<value>Keyboard Navigation:</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardControlsDescription" xml:space="preserve">
|
||||||
|
<value>
|
||||||
|
- [Shift]+S to split currently focused zone.
|
||||||
|
- Ctrl+Tab to focus zones/resizers.
|
||||||
|
- Tab to cycle zones and resizers.
|
||||||
|
- Delete to remove the focused resizer.
|
||||||
|
- Arrows to move the focused resizer.</value>
|
||||||
|
</data>
|
||||||
<data name="SplitterDescription" xml:space="preserve">
|
<data name="SplitterDescription" xml:space="preserve">
|
||||||
<value>Hold Shift key for vertical split.</value>
|
<value>Hold Shift key for vertical split.</value>
|
||||||
<comment>A segmenter visual for splitting one item into two. This would be the vertical line. Shift key is referring to key on keyboard</comment>
|
<comment>A segmenter visual for splitting one item into two. This would be the vertical line. Shift key is referring to key on keyboard</comment>
|
||||||
|
|||||||
Reference in New Issue
Block a user