From bf06f075cbe851e4115928098167ca9600001a05 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Apr 2023 02:00:40 -0400 Subject: [PATCH 1/2] Fix crash on Cursor.Parse in DevTools --- src/Avalonia.Base/Input/Cursor.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Input/Cursor.cs b/src/Avalonia.Base/Input/Cursor.cs index c555087879..49660e508e 100644 --- a/src/Avalonia.Base/Input/Cursor.cs +++ b/src/Avalonia.Base/Input/Cursor.cs @@ -42,19 +42,21 @@ namespace Avalonia.Input public class Cursor : IDisposable { public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); + private string _name; - internal Cursor(ICursorImpl platformImpl) + private Cursor(ICursorImpl platformImpl, string name) { PlatformImpl = platformImpl; + _name = name; } public Cursor(StandardCursorType cursorType) - : this(GetCursorFactory().GetCursor(cursorType)) + : this(GetCursorFactory().GetCursor(cursorType), cursorType.ToString()) { } public Cursor(IBitmap cursor, PixelPoint hotSpot) - : this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot)) + : this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot), "BitmapCursor") { } @@ -73,5 +75,10 @@ namespace Avalonia.Input { return AvaloniaLocator.Current.GetRequiredService(); } + + public override string ToString() + { + return _name; + } } } From 8ab2aa7dc922e02da61832c0aeeff27033fbbf75 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Apr 2023 02:01:42 -0400 Subject: [PATCH 2/2] Add context flyout to the dev tools tree --- .../ViewModels/TreePageViewModel.cs | 107 ++++++++++++++++++ .../Diagnostics/Views/TreePageView.xaml | 14 +++ .../Diagnostics/Views/TreePageView.xaml.cs | 11 ++ 3 files changed, 132 insertions(+) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index e916995bae..4e8b4c66a2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -1,5 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.LogicalTree; +using Avalonia.Metadata; +using Avalonia.Styling; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels @@ -21,6 +27,8 @@ namespace Avalonia.Diagnostics.ViewModels SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters(); } + public event EventHandler? ClipboardCopyRequested; + public MainViewModel MainView { get; } public FilterViewModel PropertiesFilter { get; } @@ -106,6 +114,105 @@ namespace Avalonia.Diagnostics.ViewModels } } + public void CopySelector() + { + var currentVisual = SelectedNode?.Visual as Visual; + if (currentVisual is not null) + { + var selector = GetVisualSelector(currentVisual); + + ClipboardCopyRequested?.Invoke(this, selector); + } + } + + public void CopySelectorFromTemplateParent() + { + var parts = new List(); + + var currentVisual = SelectedNode?.Visual as Visual; + while (currentVisual is not null) + { + parts.Add(GetVisualSelector(currentVisual)); + + currentVisual = currentVisual.TemplatedParent as Visual; + } + + if (parts.Any()) + { + parts.Reverse(); + var selector = string.Join(" /template/ ", parts); + + ClipboardCopyRequested?.Invoke(this, selector); + } + } + + public void ExpandRecursively() + { + if (SelectedNode is { } selectedNode) + { + ExpandNode(selectedNode); + + var stack = new Stack(); + stack.Push(selectedNode); + + while (stack.Count > 0) + { + var item = stack.Pop(); + item.IsExpanded = true; + foreach (var child in item.Children) + { + stack.Push(child); + } + } + } + } + + public void CollapseChildren() + { + if (SelectedNode is { } selectedNode) + { + var stack = new Stack(); + stack.Push(selectedNode); + + while (stack.Count > 0) + { + var item = stack.Pop(); + item.IsExpanded = false; + foreach (var child in item.Children) + { + stack.Push(child); + } + } + } + } + + public void CaptureNodeScreenshot() + { + MainView.Shot(null); + } + + public void BringIntoView() + { + (SelectedNode?.Visual as Control)?.BringIntoView(); + } + + + public void Focus() + { + (SelectedNode?.Visual as Control)?.Focus(); + } + + private static string GetVisualSelector(Visual visual) + { + var name = string.IsNullOrEmpty(visual.Name) ? "" : $"#{visual.Name}"; + var classes = string.Concat(visual.Classes + .Where(c => !c.StartsWith(":")) + .Select(c => '.' + c)); + var typeName = ((IStyleable)visual).StyleKey.Name; + + return $"{typeName}{name}{classes}"; + } + private void ExpandNode(TreeNode? node) { if (node != null) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml index 9ffc301dc1..ecdd46dd74 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml @@ -26,6 +26,20 @@ + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 1a4bb6170a..66be6b5041 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using System.Linq; using Avalonia.Controls; @@ -38,6 +39,16 @@ namespace Avalonia.Diagnostics.Views AdornerLayer.SetIsClipEnabled(_adorner, false); } + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + ((TreePageViewModel)DataContext!).ClipboardCopyRequested += (sender, s) => + { + TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(s); + }; + } + protected void AddAdorner(object? sender, PointerEventArgs e) { var node = (TreeNode?)((Control)sender!).DataContext;