diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs new file mode 100644 index 0000000000..56c933cfbd --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ControlHighlightAdorner.cs @@ -0,0 +1,47 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Reactive; + +namespace Avalonia.Diagnostics.Controls; + +internal class ControlHighlightAdorner : Control +{ + + readonly IPen _pen; + + private ControlHighlightAdorner(IPen pen) + { + _pen = pen; + this.Clip = null; + } + + public static IDisposable? Add(InputElement owner, IBrush highlightBrush) + { + + if (AdornerLayer.GetAdornerLayer(owner) is { } layer) + { + var pen = new Pen(highlightBrush, 2).ToImmutable(); + var adorner = new ControlHighlightAdorner(pen) + { + [AdornerLayer.AdornedElementProperty] = owner + }; + layer.Children.Add(adorner); + + return Disposable.Create((layer, adorner), state => + { + state.layer.Children.Remove(state.adorner); + }); + } + return default; + } + + public override void Render(DrawingContext context) + { + base.Render(context); + context.DrawRectangle(_pen, Bounds.Deflate(2)); + } + +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs new file mode 100644 index 0000000000..2216b0e9fc --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BrushSelectorConveter.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace Avalonia.Diagnostics.Converters; + +internal class BrushSelectorConveter : AvaloniaObject, IValueConverter +{ + public static readonly DirectProperty BrushProperty = + AvaloniaProperty.RegisterDirect(nameof(Brush) + , o => o.Brush + , (o, v) => o.Brush = v); + + public IBrush? Brush { get; set; } + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (ReferenceEquals(value, parameter)) + { + return Brush; + } + else if (value is ISolidColorBrush a + && parameter is ISolidColorBrush b + && a.Color == b.Color + && a.Transform == b.Transform + && b.Opacity == a.Opacity + ) + { + return Brush; + } + return null; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return BindingOperations.DoNothing; + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 3cfb0246eb..909c2baa9c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -1,5 +1,5 @@ -using System; -using Avalonia.Input; +using Avalonia.Input; +using Avalonia.Media; using Avalonia.Styling; namespace Avalonia.Diagnostics @@ -47,5 +47,10 @@ namespace Avalonia.Diagnostics /// Gets or sets whether DevTools theme. /// public ThemeVariant? ThemeVariant { get; set; } + + /// + /// Get or set Focus Highlighter + /// + public IBrush? FocusHighlighterBrush { get; set; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 4462967f03..06e4e06f8a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -9,6 +9,8 @@ using Avalonia.Threading; using Avalonia.Reactive; using Avalonia.Rendering; using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Controls.Primitives; namespace Avalonia.Diagnostics.ViewModels { @@ -31,7 +33,9 @@ namespace Avalonia.Diagnostics.ViewModels private bool _showPropertyType; private bool _showImplementedInterfaces; private readonly HashSet _pinnedProperties = new(); - + private IBrush? _FocusHighlighter; + private IDisposable? _currentFocusHighlightAdorner = default; + public MainViewModel(AvaloniaObject root) { _root = root; @@ -210,10 +214,10 @@ namespace Avalonia.Diagnostics.ViewModels private set { RaiseAndSetIfChanged(ref _focusedControl, value); } } - public IInputRoot? PointerOverRoot - { + public IInputRoot? PointerOverRoot + { get => _pointerOverRoot; - private set => RaiseAndSetIfChanged( ref _pointerOverRoot , value); + private set => RaiseAndSetIfChanged(ref _pointerOverRoot, value); } public IInputElement? PointerOverElement @@ -264,7 +268,7 @@ namespace Avalonia.Diagnostics.ViewModels _pointerOverSubscription.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); - + _currentFocusHighlightAdorner?.Dispose(); if (TryGetRenderer() is { } renderer) { renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.None; @@ -273,7 +277,20 @@ namespace Avalonia.Diagnostics.ViewModels private void UpdateFocusedControl() { - FocusedControl = KeyboardDevice.Instance?.FocusedElement?.GetType().Name; + var element = KeyboardDevice.Instance?.FocusedElement; + FocusedControl = element?.GetType().Name; + _currentFocusHighlightAdorner?.Dispose(); + if (FocusHighlighter is IBrush brush + && element is InputElement input + && TopLevel.GetTopLevel(input) is { } topLevel + && (topLevel is not Views.MainWindow)) + { + if (topLevel is PopupRoot pr && pr.ParentTopLevel is Views.MainWindow) + { + return; + } + _currentFocusHighlightAdorner = Controls.ControlHighlightAdorner.Add(input, brush); + } } private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -299,7 +316,7 @@ namespace Avalonia.Diagnostics.ViewModels } public int? StartupScreenIndex { get; private set; } = default; - + [DependsOn(nameof(TreePageViewModel.SelectedNode))] [DependsOn(nameof(Content))] public bool CanShot(object? parameter) @@ -333,12 +350,13 @@ namespace Avalonia.Diagnostics.ViewModels _screenshotHandler = options.ScreenshotHandler; StartupScreenIndex = options.StartupScreenIndex; ShowImplementedInterfaces = options.ShowImplementedInterfaces; + FocusHighlighter = options.FocusHighlighterBrush; } - public bool ShowImplementedInterfaces - { - get => _showImplementedInterfaces; - private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value); + public bool ShowImplementedInterfaces + { + get => _showImplementedInterfaces; + private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces, value); } public void ToggleShowImplementedInterfaces(object parameter) @@ -351,14 +369,25 @@ namespace Avalonia.Diagnostics.ViewModels } public bool ShowDetailsPropertyType - { - get => _showPropertyType; - private set => RaiseAndSetIfChanged(ref _showPropertyType , value); + { + get => _showPropertyType; + private set => RaiseAndSetIfChanged(ref _showPropertyType, value); } public void ToggleShowDetailsPropertyType(object parameter) { ShowDetailsPropertyType = !ShowDetailsPropertyType; } + + public IBrush? FocusHighlighter + { + get => _FocusHighlighter; + private set => RaiseAndSetIfChanged(ref _FocusHighlighter, value); + } + + public void SelectFocusHighlighter(object parameter) + { + FocusHighlighter = parameter as IBrush; + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index eac807a5bc..f810d8450c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -2,8 +2,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="using:Avalonia.Diagnostics.Views" xmlns:viewModels="using:Avalonia.Diagnostics.ViewModels" + xmlns:convertes="using:Avalonia.Diagnostics.Converters" x:Class="Avalonia.Diagnostics.Views.MainView" x:DataType="viewModels:MainViewModel"> + + 16 + + + + + @@ -61,8 +77,7 @@ IsChecked="{Binding ShowDetailsPropertyType}" IsEnabled="False"/> - - + @@ -101,6 +116,145 @@ IsEnabled="False" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +