diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs deleted file mode 100644 index 37ba5155fd..0000000000 --- a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToBrushConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; -using Avalonia.Media; - -namespace Avalonia.Diagnostics.Converters -{ - internal class BoolToBrushConverter : IValueConverter - { - public IBrush Brush { get; set; } - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return (bool)value ? Brush : Brushes.Transparent; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs index 65117dc3d1..36fe12d89c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs @@ -16,6 +16,8 @@ namespace Avalonia.Diagnostics.Models public object Handler { get; } + public bool BeginsNewRoute { get; set; } + public string HandlerName { get diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs index 60f247ead1..b56374d353 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs @@ -2,25 +2,17 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Collections; -using Avalonia.Controls; -using Avalonia.Input; using Avalonia.Interactivity; namespace Avalonia.Diagnostics.ViewModels { internal class EventOwnerTreeNode : EventTreeNodeBase { - private static readonly RoutedEvent[] s_defaultEvents = - { - Button.ClickEvent, InputElement.KeyDownEvent, InputElement.KeyUpEvent, InputElement.TextInputEvent, - InputElement.PointerReleasedEvent, InputElement.PointerPressedEvent - }; - public EventOwnerTreeNode(Type type, IEnumerable events, EventsPageViewModel vm) : base(null, type.Name) { Children = new AvaloniaList(events.OrderBy(e => e.Name) - .Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) })); + .Select(e => new EventTreeNode(this, e, vm))); IsExpanded = true; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index 9291b3bdf7..ea54302ebd 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -9,7 +9,6 @@ namespace Avalonia.Diagnostics.ViewModels { internal class EventTreeNode : EventTreeNodeBase { - private readonly RoutedEvent _event; private readonly EventsPageViewModel _parentViewModel; private bool _isRegistered; private FiredEvent _currentEvent; @@ -20,10 +19,12 @@ namespace Avalonia.Diagnostics.ViewModels Contract.Requires(@event != null); Contract.Requires(vm != null); - _event = @event; + Event = @event; _parentViewModel = vm; } + public RoutedEvent Event { get; } + public override bool? IsEnabled { get => base.IsEnabled; @@ -53,8 +54,10 @@ namespace Avalonia.Diagnostics.ViewModels { if (IsEnabled.GetValueOrDefault() && !_isRegistered) { + var allRoutes = RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble; + // FIXME: This leaks event handlers. - _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); + Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true); _isRegistered = true; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs index 1bd4117f23..c27cad29e8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs @@ -8,11 +8,13 @@ namespace Avalonia.Diagnostics.ViewModels internal bool _updateParent = true; private bool _isExpanded; private bool? _isEnabled = false; + private bool _isVisible; protected EventTreeNodeBase(EventTreeNodeBase parent, string text) { Parent = parent; Text = text; + IsVisible = true; } public IAvaloniaReadOnlyList Children @@ -33,6 +35,12 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isEnabled, value); } + public bool IsVisible + { + get => _isVisible; + set => RaiseAndSetIfChanged(ref _isVisible, value); + } + public EventTreeNodeBase Parent { get; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs index 361e82bc73..dd85fcf14c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs @@ -1,28 +1,43 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; +using System.ComponentModel; using System.Linq; using Avalonia.Controls; -using Avalonia.Data.Converters; +using Avalonia.Diagnostics.Models; +using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Media; namespace Avalonia.Diagnostics.ViewModels { internal class EventsPageViewModel : ViewModelBase { - private readonly IControl _root; + private static readonly HashSet s_defaultEvents = new HashSet() + { + Button.ClickEvent, + InputElement.KeyDownEvent, + InputElement.KeyUpEvent, + InputElement.TextInputEvent, + InputElement.PointerReleasedEvent, + InputElement.PointerPressedEvent + }; + + private readonly MainViewModel _mainViewModel; + private string _eventTypeFilter; private FiredEvent _selectedEvent; + private EventTreeNodeBase _selectedNode; - public EventsPageViewModel(IControl root) + public EventsPageViewModel(MainViewModel mainViewModel) { - _root = root; + _mainViewModel = mainViewModel; Nodes = RoutedEventRegistry.Instance.GetAllRegistered() .GroupBy(e => e.OwnerType) .OrderBy(e => e.Key.Name) .Select(g => new EventOwnerTreeNode(g.Key, g, this)) .ToArray(); + + EnableDefault(); } public string Name => "Events"; @@ -37,9 +52,140 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _selectedEvent, value); } - private void Clear() + public EventTreeNodeBase SelectedNode + { + get => _selectedNode; + set => RaiseAndSetIfChanged(ref _selectedNode, value); + } + + public string EventTypeFilter + { + get => _eventTypeFilter; + set => RaiseAndSetIfChanged(ref _eventTypeFilter, value); + } + + public void Clear() { RecordedEvents.Clear(); } + + public void DisableAll() + { + EvaluateNodeEnabled(_ => false); + } + + public void EnableDefault() + { + EvaluateNodeEnabled(node => s_defaultEvents.Contains(node.Event)); + } + + public void RequestTreeNavigateTo(EventChainLink navTarget) + { + if (navTarget.Handler is IControl control) + { + _mainViewModel.RequestTreeNavigateTo(control, true); + } + } + + public void SelectEventByType(RoutedEvent evt) + { + foreach (var node in Nodes) + { + var result = FindNode(node, evt); + + if (result != null && result.IsVisible) + { + SelectedNode = result; + + break; + } + } + + static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType) + { + if (node is EventTreeNode eventNode && eventNode.Event == eventType) + { + return node; + } + + if (node.Children != null) + { + foreach (var child in node.Children) + { + var result = FindNode(child, eventType); + + if (result != null) + { + return result; + } + } + } + + return null; + } + } + + protected override void OnPropertyChanged(PropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.PropertyName == nameof(EventTypeFilter)) + { + UpdateEventFilters(); + } + } + + private void EvaluateNodeEnabled(Func eval) + { + void ProcessNode(EventTreeNodeBase node) + { + if (node is EventTreeNode eventNode) + { + node.IsEnabled = eval(eventNode); + } + + if (node.Children != null) + { + foreach (var childNode in node.Children) + { + ProcessNode(childNode); + } + } + } + + foreach (var node in Nodes) + { + ProcessNode(node); + } + } + + private void UpdateEventFilters() + { + var filter = EventTypeFilter; + bool hasFilter = !string.IsNullOrEmpty(filter); + + foreach (var node in Nodes) + { + FilterNode(node, false); + } + + bool FilterNode(EventTreeNodeBase node, bool isParentVisible) + { + bool matchesFilter = !hasFilter || node.Text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; + bool hasVisibleChild = false; + + if (node.Children != null) + { + foreach (var childNode in node.Children) + { + hasVisibleChild |= FilterNode(childNode, matchesFilter); + } + } + + node.IsVisible = hasVisibleChild || matchesFilter || isParentVisible; + + return node.IsVisible; + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs index ae53cf6154..5fb528eead 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs @@ -62,13 +62,18 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void AddToChain(object handler, bool handled, RoutingStrategies route) - { - AddToChain(new EventChainLink(handler, handled, route)); - } - public void AddToChain(EventChainLink link) { + if (EventChain.Count > 0) + { + var prevLink = EventChain[EventChain.Count-1]; + + if (prevLink.Route != link.Route) + { + link.BeginsNewRoute = true; + } + } + EventChain.Add(link); if (HandledBy == null && link.Handled) HandledBy = link; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 3049431361..49263eafdc 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { @@ -27,7 +28,7 @@ namespace Avalonia.Diagnostics.ViewModels _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root)); - _events = new EventsPageViewModel(root); + _events = new EventsPageViewModel(this); UpdateFocusedControl(); KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged; @@ -193,5 +194,19 @@ namespace Avalonia.Diagnostics.ViewModels UpdateFocusedControl(); } } + + public void RequestTreeNavigateTo(IControl control, bool isVisualTree) + { + var tree = isVisualTree ? _visualTree : _logicalTree; + + var node = tree.FindNode(control); + + if (node != null) + { + SelectedTab = isVisualTree ? 1 : 0; + + tree.SelectControl(control); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml index b7f0860e70..d16fc40aac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml @@ -2,58 +2,129 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" - x:Class="Avalonia.Diagnostics.Views.EventsPageView"> - - - - - - - - - - - - - - + x:Class="Avalonia.Diagnostics.Views.EventsPageView" + Margin="2"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +