Skip to content

Commit

Permalink
Centralize hotkeys and hoist into DevToolsOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
stevemonaco committed May 13, 2024
1 parent 345cc32 commit 0761e38
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 67 deletions.
5 changes: 5 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,10 @@ public class DevToolsOptions
/// 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 changes: 32 additions & 0 deletions src/Avalonia.Diagnostics/Diagnostics/HotKeyConfiguration.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
using System.Collections.ObjectModel;
using Avalonia.Input;

namespace Avalonia.Diagnostics.ViewModels;

internal record HotKeyDescription(string Description, string LongDescription, string Gesture);
internal class HotKeyPageViewModel : ViewModelBase
namespace Avalonia.Diagnostics.ViewModels
{
public ObservableCollection<HotKeyDescription> HotKeys { get; } = new();
internal record HotKeyDescription(string Gesture, string BriefDescription, string? DetailedDescription = null);

public HotKeyPageViewModel()
internal class HotKeyPageViewModel : ViewModelBase
{
HotKeys = new()
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)
{
new("Enable Snapshot Frames", "Pauses refreshing the Value Frames inspector for the selected Control", "Alt+S"),
new("Disable Snapshot Frames", "Resumes refreshing the Value Frames inspector for the selected Control", "Alt+D"),
new("Inspect Control Under Pointer", "Inspects the hovered Control in the Logical or Visual Tree Page", "Ctrl+Shift"),
new("Toggle Popup Freeze", "Prevents visible Popups from closing so they can be inspected", "Ctrl+Alt+F"),
new("Screenshot Selected Control", "Saves a Screenshot of the Selected Control in the Logical or Visual Tree Page", "F8")
};
if (gesture.Key == Key.None && gesture.KeyModifiers != KeyModifiers.None)
return gesture.ToString().Replace("+None", "");
else
return gesture.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ public void SetOptions(DevToolsOptions options)
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
FocusHighlighter = options.FocusHighlighterBrush;
SelectedTab = (int)options.LaunchView;

_hotKeys.SetOptions(options);
}

public bool ShowImplementedInterfaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<TextBlock Grid.Column="2" FontWeight="Bold" Text="Gesture" />
</Grid>

<ItemsControl Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding HotKeys}">
<ItemsControl Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding HotKeyDescriptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
Expand All @@ -26,7 +26,7 @@
<ColumnDefinition SharedSizeGroup="B" Width="*" />
</Grid.ColumnDefinitions>

<TextBlock Text="{Binding Description}" ToolTip.Tip="{Binding LongDescription}" />
<TextBlock Text="{Binding BriefDescription}" ToolTip.Tip="{Binding DetailedDescription}" />
<TextBlock Grid.Column="2" Text="{Binding Gesture}" />
</Grid>
</DataTemplate>
Expand Down
3 changes: 0 additions & 3 deletions src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 86 additions & 49 deletions src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal class MainWindow : Window, IStyleHost
private readonly HashSet<Popup> _frozenPopupStates;
private AvaloniaObject? _root;
private PixelPoint _lastPointerPosition;
private HotKeyConfiguration? _hotKeys;

public MainWindow()
{
Expand Down Expand Up @@ -169,83 +170,117 @@ void ProcessProperty<T>(Control control, AvaloniaProperty<T> property)

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)
Expand All @@ -261,6 +296,8 @@ private void PopupOnClosing(object? sender, CancelEventArgs e)

public void SetOptions(DevToolsOptions options)
{
_hotKeys = options.HotKeys;

(DataContext as MainViewModel)?.SetOptions(options);
if (options.ThemeVariant is { } themeVariant)
{
Expand Down

0 comments on commit 0761e38

Please sign in to comment.