Browse Source

Add HotKeys Page to DevTools (#15700)

* Add HotKeys Page to DevTools

* Centralize hotkeys and hoist into DevToolsOptions
pull/15722/head
Stephen Monaco 2 years ago
committed by GitHub
parent
commit
0ae36f98ba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  2. 32
      src/Avalonia.Diagnostics/Diagnostics/HotKeyConfiguration.cs
  3. 40
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/HotKeyPageViewModel.cs
  4. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  5. 36
      src/Avalonia.Diagnostics/Diagnostics/Views/HotKeyPageView.axaml
  6. 18
      src/Avalonia.Diagnostics/Diagnostics/Views/HotKeyPageView.axaml.cs
  7. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  8. 3
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  9. 135
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

5
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -57,5 +57,10 @@ namespace Avalonia.Diagnostics
/// Set the <see cref="DevToolsViewKind">kind</see> of diagnostic view that show at launch of DevTools
/// </summary>
public DevToolsViewKind LaunchView { get; init; }
/// <summary>
/// Gets or inits the <see cref="HotKeyConfiguration" /> used to activate DevTools features
/// </summary>
internal HotKeyConfiguration HotKeys { get; init; } = new();
}
}

32
src/Avalonia.Diagnostics/Diagnostics/HotKeyConfiguration.cs

@ -0,0 +1,32 @@
using Avalonia.Input;
namespace Avalonia.Diagnostics
{
internal class HotKeyConfiguration
{
/// <summary>
/// Freezes refreshing the Value Frames inspector for the selected Control
/// </summary>
public KeyGesture ValueFramesFreeze { get; init; } = new(Key.S, KeyModifiers.Alt);
/// <summary>
/// Resumes refreshing the Value Frames inspector for the selected Control
/// </summary>
public KeyGesture ValueFramesUnfreeze { get; init; } = new(Key.D, KeyModifiers.Alt);
/// <summary>
/// Inspects the hovered Control in the Logical or Visual Tree Page
/// </summary>
public KeyGesture InspectHoveredControl { get; init; } = new(Key.None, KeyModifiers.Shift | KeyModifiers.Control);
/// <summary>
/// Toggles the freezing of Popups which prevents visible Popups from closing so they can be inspected
/// </summary>
public KeyGesture TogglePopupFreeze { get; init; } = new(Key.F, KeyModifiers.Alt | KeyModifiers.Control);
/// <summary>
/// Saves a Screenshot of the Selected Control in the Logical or Visual Tree Page
/// </summary>
public KeyGesture ScreenshotSelectedControl { get; init; } = new(Key.F8);
}
}

40
src/Avalonia.Diagnostics/Diagnostics/ViewModels/HotKeyPageViewModel.cs

@ -0,0 +1,40 @@
using System.Collections.ObjectModel;
using Avalonia.Input;
namespace Avalonia.Diagnostics.ViewModels
{
internal record HotKeyDescription(string Gesture, string BriefDescription, string? DetailedDescription = null);
internal class HotKeyPageViewModel : ViewModelBase
{
private ObservableCollection<HotKeyDescription>? _hotKeyDescriptions;
public ObservableCollection<HotKeyDescription>? HotKeyDescriptions
{
get => _hotKeyDescriptions;
private set => RaiseAndSetIfChanged(ref _hotKeyDescriptions, value);
}
public void SetOptions(DevToolsOptions options)
{
var hotKeys = options.HotKeys;
HotKeyDescriptions = new()
{
new(CreateDescription(options.Gesture), "Launch DevTools", "Launches DevTools to inspect the TopLevel that received the hotkey input"),
new(CreateDescription(hotKeys.ValueFramesFreeze), "Freeze Value Frames", "Pauses refreshing the Value Frames inspector for the selected Control"),
new(CreateDescription(hotKeys.ValueFramesUnfreeze), "Unfreeze Value Frames", "Resumes refreshing the Value Frames inspector for the selected Control"),
new(CreateDescription(hotKeys.InspectHoveredControl), "Inspect Control Under Pointer", "Inspects the hovered Control in the Logical or Visual Tree Page"),
new(CreateDescription(hotKeys.TogglePopupFreeze), "Toggle Popup Freeze", "Prevents visible Popups from closing so they can be inspected"),
new(CreateDescription(hotKeys.ScreenshotSelectedControl), "Screenshot Selected Control", "Saves a Screenshot of the Selected Control in the Logical or Visual Tree Page")
};
}
private string CreateDescription(KeyGesture gesture)
{
if (gesture.Key == Key.None && gesture.KeyModifiers != KeyModifiers.None)
return gesture.ToString().Replace("+None", "");
else
return gesture.ToString();
}
}
}

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -18,6 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly TreePageViewModel _logicalTree;
private readonly TreePageViewModel _visualTree;
private readonly EventsPageViewModel _events;
private readonly HotKeyPageViewModel _hotKeys;
private readonly IDisposable _pointerOverSubscription;
private ViewModelBase? _content;
private int _selectedTab;
@ -40,6 +41,7 @@ namespace Avalonia.Diagnostics.ViewModels
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root), _pinnedProperties);
_visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root), _pinnedProperties);
_events = new EventsPageViewModel(this);
_hotKeys = new HotKeyPageViewModel();
UpdateFocusedControl();
@ -194,6 +196,9 @@ namespace Avalonia.Diagnostics.ViewModels
case 2:
Content = _events;
break;
case 3:
Content = _hotKeys;
break;
default:
Content = _logicalTree;
break;
@ -231,6 +236,11 @@ namespace Avalonia.Diagnostics.ViewModels
private set => RaiseAndSetIfChanged(ref _pointerOverElementName, value);
}
public void ShowHotKeys()
{
SelectedTab = 3;
}
public void SelectControl(Control control)
{
var tree = Content as TreePageViewModel;
@ -333,6 +343,8 @@ namespace Avalonia.Diagnostics.ViewModels
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
FocusHighlighter = options.FocusHighlighterBrush;
SelectedTab = (int)options.LaunchView;
_hotKeys.SetOptions(options);
}
public bool ShowImplementedInterfaces

36
src/Avalonia.Diagnostics/Diagnostics/Views/HotKeyPageView.axaml

@ -0,0 +1,36 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Avalonia.Diagnostics.ViewModels"
Padding="4"
x:Class="Avalonia.Diagnostics.Views.HotKeyPageView"
x:DataType="vm:HotKeyPageViewModel">
<Grid RowDefinitions="auto,*" Grid.IsSharedSizeScope="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" Width="auto" />
<ColumnDefinition Width="8" />
<ColumnDefinition SharedSizeGroup="B" Width="*" />
</Grid.ColumnDefinitions>
<TextBlock FontWeight="Bold" Text="Action" />
<TextBlock Grid.Column="2" FontWeight="Bold" Text="Gesture" />
</Grid>
<ItemsControl Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding HotKeyDescriptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" Width="auto" />
<ColumnDefinition Width="8" />
<ColumnDefinition SharedSizeGroup="B" Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding BriefDescription}" ToolTip.Tip="{Binding DetailedDescription}" />
<TextBlock Grid.Column="2" Text="{Binding Gesture}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>

18
src/Avalonia.Diagnostics/Diagnostics/Views/HotKeyPageView.axaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Avalonia.Diagnostics.Views
{
internal class HotKeyPageView : UserControl
{
public HotKeyPageView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

2
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@ -72,6 +72,7 @@
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_HotKeys" Command="{Binding ShowHotKeys}" />
</MenuItem>
<MenuItem Header="_Overlays">
<MenuItem Header="Margin/padding" Command="{Binding ToggleVisualizeMarginPadding}">
@ -255,6 +256,7 @@
<TabStripItem Content="Logical Tree" />
<TabStripItem Content="Visual Tree" />
<TabStripItem Content="Events" />
<TabStripItem Content="HotKeys" IsVisible="False" />
</TabStrip>
<ContentControl Grid.Row="2"

3
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@ -18,8 +18,5 @@
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
</Window.Styles>
<Window.KeyBindings>
<KeyBinding Gesture="F8" Command="{Binding Shot}"/>
</Window.KeyBindings>
<views:MainView/>
</Window>

135
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -23,6 +23,7 @@ namespace Avalonia.Diagnostics.Views
private readonly HashSet<Popup> _frozenPopupStates;
private AvaloniaObject? _root;
private PixelPoint _lastPointerPosition;
private HotKeyConfiguration? _hotKeys;
public MainWindow()
{
@ -169,83 +170,117 @@ namespace Avalonia.Diagnostics.Views
private void RawKeyDown(RawKeyEventArgs e)
{
var vm = (MainViewModel?)DataContext;
if (vm is null)
if (_hotKeys is null ||
DataContext is not MainViewModel vm ||
vm.PointerOverRoot is not TopLevel root)
{
return;
}
var root = vm.PointerOverRoot as TopLevel;
if (root is PopupRoot pr && pr.ParentTopLevel != null)
{
root = pr.ParentTopLevel;
}
var modifiers = MergeModifiers(e.Key, e.Modifiers.ToKeyModifiers());
if (root is null)
if (IsMatched(_hotKeys.ValueFramesFreeze, e.Key, modifiers))
{
return;
FreezeValueFrames(vm);
}
else if (IsMatched(_hotKeys.ValueFramesUnfreeze, e.Key, modifiers))
{
UnfreezeValueFrames(vm);
}
else if (IsMatched(_hotKeys.TogglePopupFreeze, e.Key, modifiers))
{
ToggleFreezePopups(root, vm);
}
else if (IsMatched(_hotKeys.ScreenshotSelectedControl, e.Key, modifiers))
{
ScreenshotSelectedControl(vm);
}
else if (IsMatched(_hotKeys.InspectHoveredControl, e.Key, modifiers))
{
InspectHoveredControl(root, vm);
}
if (root is PopupRoot pr && pr.ParentTopLevel != null)
static bool IsMatched(KeyGesture gesture, Key key, KeyModifiers modifiers)
{
root = pr.ParentTopLevel;
return (gesture.Key == key || gesture.Key == Key.None) && modifiers.HasAllFlags(gesture.KeyModifiers);
}
switch (e.Modifiers)
// When Control, Shift, or Alt are initially pressed, they are the Key and not part of Modifiers
// This merges so modifier keys alone can more easily trigger actions
static KeyModifiers MergeModifiers(Key key, KeyModifiers modifiers)
{
case RawInputModifiers.Control when (e.Key == Key.LeftShift || e.Key == Key.RightShift):
case RawInputModifiers.Shift when (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl):
case RawInputModifiers.Shift | RawInputModifiers.Control:
return key switch
{
Control? control = null;
Key.LeftCtrl or Key.RightCtrl => modifiers | KeyModifiers.Control,
Key.LeftShift or Key.RightShift => modifiers | KeyModifiers.Shift,
Key.LeftAlt or Key.RightAlt => modifiers | KeyModifiers.Alt,
_ => modifiers
};
}
}
foreach (var popupRoot in GetPopupRoots(root))
{
control = GetHoveredControl(popupRoot);
private void FreezeValueFrames(MainViewModel vm)
{
vm.EnableSnapshotStyles(true);
}
if (control != null)
{
break;
}
}
private void UnfreezeValueFrames(MainViewModel vm)
{
vm.EnableSnapshotStyles(false);
}
control ??= GetHoveredControl(root);
private void ToggleFreezePopups(TopLevel root, MainViewModel vm)
{
vm.FreezePopups = !vm.FreezePopups;
if (control != null)
foreach (var popupRoot in GetPopupRoots(root))
{
if (popupRoot.Parent is Popup popup)
{
if (vm.FreezePopups)
{
vm.SelectControl(control);
popup.Closing += PopupOnClosing;
_frozenPopupStates.Add(popup);
}
else
{
popup.Closing -= PopupOnClosing;
_frozenPopupStates.Remove(popup);
}
break;
}
}
}
case RawInputModifiers.Control | RawInputModifiers.Alt when e.Key == Key.F:
{
vm.FreezePopups = !vm.FreezePopups;
private void ScreenshotSelectedControl(MainViewModel vm)
{
vm.Shot(null);
}
foreach (var popupRoot in GetPopupRoots(root))
{
if (popupRoot.Parent is Popup popup)
{
if (vm.FreezePopups)
{
popup.Closing += PopupOnClosing;
_frozenPopupStates.Add(popup);
}
else
{
popup.Closing -= PopupOnClosing;
_frozenPopupStates.Remove(popup);
}
}
}
private void InspectHoveredControl(TopLevel root, MainViewModel vm)
{
Control? control = null;
break;
}
foreach (var popupRoot in GetPopupRoots(root))
{
control = GetHoveredControl(popupRoot);
case RawInputModifiers.Alt when e.Key == Key.S || e.Key == Key.D:
if (control != null)
{
vm.EnableSnapshotStyles(e.Key == Key.S);
break;
}
}
control ??= GetHoveredControl(root);
if (control != null)
{
vm.SelectControl(control);
}
}
private void PopupOnClosing(object? sender, CancelEventArgs e)
@ -261,6 +296,8 @@ namespace Avalonia.Diagnostics.Views
public void SetOptions(DevToolsOptions options)
{
_hotKeys = options.HotKeys;
(DataContext as MainViewModel)?.SetOptions(options);
if (options.ThemeVariant is { } themeVariant)
{

Loading…
Cancel
Save