mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-24 04:12:32 +08: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:
parent
f0750997de
commit
f10faf004e
@ -12,6 +12,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Utils;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Common.UI;
|
||||
@ -178,6 +179,11 @@ namespace FancyZonesEditor
|
||||
{
|
||||
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)
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using FancyZonesEditor.Models;
|
||||
|
||||
@ -38,9 +40,20 @@ namespace FancyZonesEditor
|
||||
InitializeComponent();
|
||||
Loaded += GridEditor_Loaded;
|
||||
Unloaded += GridEditor_Unloaded;
|
||||
KeyDown += GridEditor_KeyDown;
|
||||
KeyUp += GridEditor_KeyUp;
|
||||
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)
|
||||
{
|
||||
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
|
||||
@ -58,6 +71,134 @@ namespace FancyZonesEditor
|
||||
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)
|
||||
{
|
||||
((App)Application.Current).MainWindowSettings.PropertyChanged -= ZoneSettings_PropertyChanged;
|
||||
@ -267,7 +408,7 @@ namespace FancyZonesEditor
|
||||
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
|
||||
if (resizer.Orientation == Orientation.Vertical)
|
||||
@ -328,6 +469,12 @@ namespace FancyZonesEditor
|
||||
{
|
||||
GridResizer resizer = (GridResizer)sender;
|
||||
int resizerIndex = AdornerLayer.Children.IndexOf(resizer);
|
||||
if (resizerIndex == -1)
|
||||
{
|
||||
// Resizer was removed during drag
|
||||
return;
|
||||
}
|
||||
|
||||
Size actualSize = WorkAreaSize();
|
||||
|
||||
double pixelDelta = resizer.Orientation == Orientation.Vertical ?
|
||||
|
@ -53,6 +53,14 @@
|
||||
Text="{x:Static props:Resources.MergeName}" />
|
||||
<Run Text="{x:Static props:Resources.MergeDescription}" />
|
||||
</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>
|
||||
<Grid Margin="0,24,0,-4">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:FancyZonesEditor"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<Thumb.Template>
|
||||
<ControlTemplate>
|
||||
@ -35,6 +36,10 @@
|
||||
TargetName="Body"
|
||||
Value="{DynamicResource SystemAccentColorLight1Brush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||
<Setter Property="Background" TargetName="Body"
|
||||
Value="{DynamicResource SystemAccentColorLight3Brush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Thumb.Template>
|
||||
|
@ -13,6 +13,8 @@
|
||||
BorderBrush="{DynamicResource SystemControlBackgroundAccentBrush}"
|
||||
BorderThickness="1"
|
||||
Opacity="1"
|
||||
Focusable="True"
|
||||
IsTabStop="True"
|
||||
ui:ControlHelper.CornerRadius="4"
|
||||
mc:Ignorable="d">
|
||||
|
||||
|
@ -23,6 +23,7 @@ namespace FancyZonesEditor
|
||||
private const string GridZoneBackgroundBrushID = "GridZoneBackgroundBrush";
|
||||
private const string SecondaryForegroundBrushID = "SecondaryForegroundBrush";
|
||||
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));
|
||||
|
||||
@ -75,12 +76,39 @@ namespace FancyZonesEditor
|
||||
|
||||
SizeChanged += GridZone_SizeChanged;
|
||||
|
||||
GotKeyboardFocus += GridZone_GotKeyboardFocus;
|
||||
LostKeyboardFocus += GridZone_LostKeyboardFocus;
|
||||
|
||||
_snapX = snapX;
|
||||
_snapY = snapY;
|
||||
_canSplit = canSplit;
|
||||
_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)
|
||||
{
|
||||
// using current culture as this is end user facing
|
||||
@ -241,7 +269,7 @@ namespace FancyZonesEditor
|
||||
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));
|
||||
}
|
||||
|
@ -253,10 +253,19 @@ namespace FancyZonesEditor
|
||||
|
||||
public void FocusEditor()
|
||||
{
|
||||
if (_editorLayout != null && _editorLayout is CanvasEditor canvasEditor)
|
||||
if (_editorLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_editorLayout is CanvasEditor canvasEditor)
|
||||
{
|
||||
canvasEditor.FocusZone();
|
||||
}
|
||||
else if (_editorLayout is GridEditor gridEditor)
|
||||
{
|
||||
gridEditor.FocusZone();
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// Looks up a localized string similar to Create layouts that have overlapping zones.
|
||||
/// </summary>
|
||||
|
@ -336,6 +336,17 @@
|
||||
<value>Merge/Delete:</value>
|
||||
<comment>Title for concept behind Merging two zones together or removing an zone</comment>
|
||||
</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">
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user