From 8ab2aa7dc922e02da61832c0aeeff27033fbbf75 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Apr 2023 02:01:42 -0400 Subject: [PATCH] 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;