diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index ead5b0d9f3..0c8fc31df1 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
@@ -21,7 +21,7 @@ namespace Avalonia.Controls
///
/// A control context menu.
///
- public class ContextMenu : MenuBase, ISetterValue
+ public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider
{
///
/// Defines the property.
@@ -82,6 +82,7 @@ namespace Avalonia.Controls
private Popup? _popup;
private List? _attachedControls;
private IInputElement? _previousFocus;
+ private Action? _popupHostChangedHandler;
///
/// Initializes a new instance of the class.
@@ -304,6 +305,14 @@ namespace Avalonia.Controls
}
}
+ IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
@@ -364,6 +373,8 @@ namespace Avalonia.Controls
{
_previousFocus = FocusManager.Instance?.Current;
Focus();
+
+ _popupHostChangedHandler?.Invoke(_popup!.Host);
}
private void PopupClosing(object sender, CancelEventArgs e)
@@ -397,6 +408,8 @@ namespace Avalonia.Controls
RoutedEvent = MenuClosedEvent,
Source = this,
});
+
+ _popupHostChangedHandler?.Invoke(null);
}
private void PopupKeyUp(object sender, KeyEventArgs e)
diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
new file mode 100644
index 0000000000..11d3b1792a
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
@@ -0,0 +1,23 @@
+using System;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Diagnostics interface to retrieve an associated .
+ ///
+ public interface IPopupHostProvider
+ {
+ ///
+ /// The popup host.
+ ///
+ IPopupHost? PopupHost { get; }
+
+ ///
+ /// Raised when the popup host changes.
+ ///
+ event Action? PopupHostChanged;
+ }
+}
diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
new file mode 100644
index 0000000000..4acf2a217f
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
@@ -0,0 +1,15 @@
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Helper class to provide diagnostics information for .
+ ///
+ public static class ToolTipDiagnostics
+ {
+ ///
+ /// Provides access to the internal for use in DevTools.
+ ///
+ public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty;
+ }
+}
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 8448dde21e..230b4954fe 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -1,19 +1,18 @@
using System;
using System.ComponentModel;
+using Avalonia.Controls.Diagnostics;
using System.Linq;
-
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
-using Avalonia.Rendering;
#nullable enable
namespace Avalonia.Controls.Primitives
{
- public abstract class FlyoutBase : AvaloniaObject
+ public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider
{
static FlyoutBase()
{
@@ -59,6 +58,7 @@ namespace Avalonia.Controls.Primitives
private Rect? _enlargedPopupRect;
private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable;
+ private Action? _popupHostChangedHandler;
public FlyoutBase()
{
@@ -103,6 +103,14 @@ namespace Avalonia.Controls.Primitives
private set => SetAndRaise(TargetProperty, ref _target, value);
}
+ IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
public event EventHandler? Closed;
public event EventHandler? Closing;
public event EventHandler? Opened;
@@ -363,6 +371,8 @@ namespace Avalonia.Controls.Primitives
private void OnPopupOpened(object sender, EventArgs e)
{
IsOpen = true;
+
+ _popupHostChangedHandler?.Invoke(Popup!.Host);
}
private void OnPopupClosing(object sender, CancelEventArgs e)
@@ -376,6 +386,8 @@ namespace Avalonia.Controls.Primitives
private void OnPopupClosed(object sender, EventArgs e)
{
HideCore(false);
+
+ _popupHostChangedHandler?.Invoke(null);
}
// This method is handling both popup logical tree and target logical tree.
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index f23a27e67a..d5fb69a672 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
@@ -18,7 +19,7 @@ namespace Avalonia.Controls.Primitives
///
/// Displays a popup window.
///
- public class Popup : Control, IVisualTreeHost
+ public class Popup : Control, IVisualTreeHost, IPopupHostProvider
{
public static readonly StyledProperty WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), true);
@@ -134,6 +135,7 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState;
private IInputElement _overlayInputPassThroughElement;
+ private Action? _popupHostChangedHandler;
///
/// Initializes static members of the class.
@@ -351,6 +353,14 @@ namespace Avalonia.Controls.Primitives
///
IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot;
+ IPopupHost? IPopupHostProvider.PopupHost => Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
///
/// Opens the popup.
///
@@ -482,6 +492,8 @@ namespace Avalonia.Controls.Primitives
}
Opened?.Invoke(this, EventArgs.Empty);
+
+ _popupHostChangedHandler?.Invoke(Host);
}
///
@@ -591,6 +603,8 @@ namespace Avalonia.Controls.Primitives
_openState.Dispose();
_openState = null;
+ _popupHostChangedHandler?.Invoke(null);
+
using (BeginIgnoringIsOpen())
{
IsOpen = false;
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index ab507d07a2..ab310d60ef 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -1,9 +1,8 @@
#nullable enable
using System;
-using System.Reactive.Linq;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -17,7 +16,7 @@ namespace Avalonia.Controls
/// assigning the content that you want displayed.
///
[PseudoClasses(":open")]
- public class ToolTip : ContentControl
+ public class ToolTip : ContentControl, IPopupHostProvider
{
///
/// Defines the ToolTip.Tip attached property.
@@ -61,7 +60,8 @@ namespace Avalonia.Controls
internal static readonly AttachedProperty ToolTipProperty =
AvaloniaProperty.RegisterAttached("ToolTip");
- private IPopupHost? _popup;
+ private IPopupHost? _popupHost;
+ private Action? _popupHostChangedHandler;
///
/// Initializes static members of the class.
@@ -251,35 +251,45 @@ namespace Avalonia.Controls
tooltip.RecalculatePosition(control);
}
+
+ IPopupHost? IPopupHostProvider.PopupHost => _popupHost;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
internal void RecalculatePosition(Control control)
{
- _popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
+ _popupHost?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
}
private void Open(Control control)
{
Close();
- _popup = OverlayPopupHost.CreatePopupHost(control, null);
- _popup.SetChild(this);
- ((ISetLogicalParent)_popup).SetParent(control);
+ _popupHost = OverlayPopupHost.CreatePopupHost(control, null);
+ _popupHost.SetChild(this);
+ ((ISetLogicalParent)_popupHost).SetParent(control);
- _popup.ConfigurePosition(control, GetPlacement(control),
+ _popupHost.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
- WindowManagerAddShadowHintChanged(_popup, false);
+ WindowManagerAddShadowHintChanged(_popupHost, false);
- _popup.Show();
+ _popupHost.Show();
+ _popupHostChangedHandler?.Invoke(_popupHost);
}
private void Close()
{
- if (_popup != null)
+ if (_popupHost != null)
{
- _popup.SetChild(null);
- _popup.Dispose();
- _popup = null;
+ _popupHost.SetChild(null);
+ _popupHost.Dispose();
+ _popupHost = null;
+ _popupHostChangedHandler?.Invoke(null);
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 3f367165ac..d0a4ad38c5 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
-
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
@@ -22,6 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels
private bool _shouldVisualizeMarginPadding = true;
private bool _shouldVisualizeDirtyRects;
private bool _showFpsOverlay;
+ private bool _freezePopups;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
@@ -41,6 +41,12 @@ namespace Avalonia.Diagnostics.ViewModels
Console = new ConsoleViewModel(UpdateConsoleContext);
}
+ public bool FreezePopups
+ {
+ get => _freezePopups;
+ set => RaiseAndSetIfChanged(ref _freezePopups, value);
+ }
+
public bool ShouldVisualizeMarginPadding
{
get => _shouldVisualizeMarginPadding;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
index 4cb470eeac..94707ac189 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
@@ -1,26 +1,28 @@
using System;
-using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
+using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class TreeNode : ViewModelBase, IDisposable
{
- private IDisposable? _classesSubscription;
+ private readonly IDisposable? _classesSubscription;
private string _classes;
private bool _isExpanded;
- public TreeNode(IVisual visual, TreeNode? parent)
+ protected TreeNode(IVisual visual, TreeNode? parent, string? customName = null)
{
+ _classes = string.Empty;
Parent = parent;
- Type = visual.GetType().Name;
+ Type = customName ?? visual.GetType().Name;
Visual = visual;
- _classes = string.Empty;
+ FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal;
if (visual is IControl control)
{
@@ -52,6 +54,12 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
+ private bool IsRoot => Visual is TopLevel ||
+ Visual is ContextMenu ||
+ Visual is IPopupHost;
+
+ public FontWeight FontWeight { get; }
+
public abstract TreeNodeCollection Children
{
get;
@@ -95,20 +103,5 @@ namespace Avalonia.Diagnostics.ViewModels
_classesSubscription?.Dispose();
Children.Dispose();
}
-
- private static int IndexOf(IReadOnlyList collection, TreeNode item)
- {
- var count = collection.Count;
-
- for (var i = 0; i < count; ++i)
- {
- if (collection[i] == item)
- {
- return i;
- }
- }
-
- throw new AvaloniaInternalException("TreeNode was not present in parent Children collection.");
- }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
index 48fa636664..6a430897ba 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
@@ -1,5 +1,10 @@
using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Controls.Diagnostics;
+using Avalonia.Controls.Primitives;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -7,31 +12,30 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class VisualTreeNode : TreeNode
{
- public VisualTreeNode(IVisual visual, TreeNode? parent)
- : base(visual, parent)
+ public VisualTreeNode(IVisual visual, TreeNode? parent, string? customName = null)
+ : base(visual, parent, customName)
{
Children = new VisualTreeNodeCollection(this, visual);
- if ((Visual is IStyleable styleable))
- {
+ if (Visual is IStyleable styleable)
IsInTemplate = styleable.TemplatedParent != null;
- }
}
- public bool IsInTemplate { get; private set; }
+ public bool IsInTemplate { get; }
public override TreeNodeCollection Children { get; }
public static VisualTreeNode[] Create(object control)
{
- var visual = control as IVisual;
- return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty();
+ return control is IVisual visual ?
+ new[] { new VisualTreeNode(visual, null) } :
+ Array.Empty();
}
internal class VisualTreeNodeCollection : TreeNodeCollection
{
private readonly IVisual _control;
- private IDisposable? _subscription;
+ private readonly CompositeDisposable _subscriptions = new CompositeDisposable(2);
public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner)
@@ -41,15 +45,106 @@ namespace Avalonia.Diagnostics.ViewModels
public override void Dispose()
{
- _subscription?.Dispose();
+ _subscriptions.Dispose();
+ }
+
+ private static IObservable? GetHostedPopupRootObservable(IVisual visual)
+ {
+ static IObservable GetPopupHostObservable(
+ IPopupHostProvider popupHostProvider,
+ string? providerName = null)
+ {
+ return Observable.FromEvent(
+ x => popupHostProvider.PopupHostChanged += x,
+ x => popupHostProvider.PopupHostChanged -= x)
+ .StartWith(popupHostProvider.PopupHost)
+ .Select(popupHost =>
+ {
+ if (popupHost is IControl control)
+ return new PopupRoot(
+ control,
+ providerName != null ? $"{providerName} ({control.GetType().Name})" : null);
+
+ return (PopupRoot?)null;
+ });
+ }
+
+ return visual switch
+ {
+ Popup p => GetPopupHostObservable(p),
+ Control c => Observable.CombineLatest(
+ c.GetObservable(Control.ContextFlyoutProperty),
+ c.GetObservable(Control.ContextMenuProperty),
+ c.GetObservable(FlyoutBase.AttachedFlyoutProperty),
+ c.GetObservable(ToolTipDiagnostics.ToolTipProperty),
+ (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip) =>
+ {
+ if (ContextMenu != null)
+ //Note: ContextMenus are special since all the items are added as visual children.
+ //So we don't need to go via Popup
+ return Observable.Return(new PopupRoot(ContextMenu));
+
+ if (ContextFlyout != null)
+ return GetPopupHostObservable(ContextFlyout, "ContextFlyout");
+
+ if (AttachedFlyout != null)
+ return GetPopupHostObservable(AttachedFlyout, "AttachedFlyout");
+
+ if (ToolTip != null)
+ return GetPopupHostObservable(ToolTip, "ToolTip");
+
+ return Observable.Return(null);
+ })
+ .Switch(),
+ _ => null
+ };
}
protected override void Initialize(AvaloniaList nodes)
{
- _subscription = _control.VisualChildren.ForEachItem(
- (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
- (i, item) => nodes.RemoveAt(i),
- () => nodes.Clear());
+ _subscriptions.Clear();
+
+ if (GetHostedPopupRootObservable(_control) is { } popupRootObservable)
+ {
+ VisualTreeNode? childNode = null;
+
+ _subscriptions.Add(
+ popupRootObservable
+ .Subscribe(popupRoot =>
+ {
+ if (popupRoot != null)
+ {
+ childNode = new VisualTreeNode(
+ popupRoot.Value.Root,
+ Owner,
+ popupRoot.Value.CustomName);
+
+ nodes.Add(childNode);
+ }
+ else if (childNode != null)
+ {
+ nodes.Remove(childNode);
+ }
+ }));
+ }
+
+ _subscriptions.Add(
+ _control.VisualChildren.ForEachItem(
+ (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
+ (i, item) => nodes.RemoveAt(i),
+ () => nodes.Clear()));
+ }
+
+ private struct PopupRoot
+ {
+ public PopupRoot(IControl root, string? customName = null)
+ {
+ Root = root;
+ CustomName = customName;
+ }
+
+ public IControl Root { get; }
+ public string? CustomName { get; }
}
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 8c4db33f91..6f2ac96a66 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -5,14 +5,14 @@
-
+
-
-
-
+
+
+
+ Content="{Binding Content}" />
-
+ IsVisible="False" />
+
+ IsVisible="{Binding IsVisible}" />
+
-
- Hold Ctrl+Shift over a control to inspect.
-
- Focused:
-
-
- Pointer Over:
-
-
+
+
+ Hold Ctrl+Shift over a control to inspect.
+
+ Focused:
+
+
+ Pointer Over:
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
index d1232b749a..ea06c33e4d 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
+using Avalonia.Data;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Input.Raw;
@@ -15,6 +18,7 @@ namespace Avalonia.Diagnostics.Views
internal class MainWindow : Window, IStyleHost
{
private readonly IDisposable _keySubscription;
+ private readonly Dictionary _frozenPopupStates;
private TopLevel? _root;
public MainWindow()
@@ -23,23 +27,26 @@ namespace Avalonia.Diagnostics.Views
_keySubscription = InputManager.Instance.Process
.OfType()
+ .Where(x => x.Type == RawKeyEventType.KeyDown)
.Subscribe(RawKeyDown);
+ _frozenPopupStates = new Dictionary();
+
EventHandler? lh = default;
lh = (s, e) =>
- {
- this.Opened -= lh;
- if ((DataContext as MainViewModel)?.StartupScreenIndex is int index)
- {
- var screens = this.Screens;
- if (index > -1 && index < screens.ScreenCount)
- {
- var screen = screens.All[index];
- this.Position = screen.Bounds.TopLeft;
- this.WindowState = WindowState.Maximized;
- }
- }
- };
+ {
+ this.Opened -= lh;
+ if ((DataContext as MainViewModel)?.StartupScreenIndex is { } index)
+ {
+ var screens = this.Screens;
+ if (index > -1 && index < screens.ScreenCount)
+ {
+ var screen = screens.All[index];
+ this.Position = screen.Bounds.TopLeft;
+ this.WindowState = WindowState.Maximized;
+ }
+ }
+ };
this.Opened += lh;
}
@@ -77,6 +84,13 @@ namespace Avalonia.Diagnostics.Views
base.OnClosed(e);
_keySubscription.Dispose();
+ foreach (var state in _frozenPopupStates)
+ {
+ state.Value.Dispose();
+ }
+
+ _frozenPopupStates.Clear();
+
if (_root != null)
{
_root.Closed -= RootClosed;
@@ -91,6 +105,53 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this);
}
+ private IControl? GetHoveredControl(TopLevel topLevel)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ var point = (topLevel as IInputRoot)?.MouseDevice?.GetPosition(topLevel) ?? default;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+ return (IControl?)topLevel.GetVisualsAt(point, x =>
+ {
+ if (x is AdornerLayer || !x.IsVisible)
+ {
+ return false;
+ }
+
+ return !(x is IInputElement ie) || ie.IsHitTestVisible;
+ })
+ .FirstOrDefault();
+ }
+
+ private static List GetPopupRoots(IVisual root)
+ {
+ var popupRoots = new List();
+
+ void ProcessProperty(IControl control, AvaloniaProperty property)
+ {
+ if (control.GetValue(property) is IPopupHostProvider popupProvider
+ && popupProvider.PopupHost is PopupRoot popupRoot)
+ {
+ popupRoots.Add(popupRoot);
+ }
+ }
+
+ foreach (var control in root.GetVisualDescendants().OfType())
+ {
+ if (control is Popup p && p.Host is PopupRoot popupRoot)
+ {
+ popupRoots.Add(popupRoot);
+ }
+
+ ProcessProperty(control, ContextFlyoutProperty);
+ ProcessProperty(control, ContextMenuProperty);
+ ProcessProperty(control, FlyoutBase.AttachedFlyoutProperty);
+ ProcessProperty(control, ToolTipDiagnostics.ToolTipProperty);
+ }
+
+ return popupRoots;
+ }
+
private void RawKeyDown(RawKeyEventArgs e)
{
var vm = (MainViewModel?)DataContext;
@@ -99,34 +160,72 @@ namespace Avalonia.Diagnostics.Views
return;
}
- const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift;
-
- if (e.Modifiers == modifiers)
+ switch (e.Modifiers)
{
-#pragma warning disable CS0618 // Type or member is obsolete
- var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default;
-#pragma warning restore CS0618 // Type or member is obsolete
+ case RawInputModifiers.Control | RawInputModifiers.Shift:
+ {
+ IControl? control = null;
+
+ foreach (var popupRoot in GetPopupRoots(Root))
+ {
+ control = GetHoveredControl(popupRoot);
+
+ if (control != null)
+ {
+ break;
+ }
+ }
+
+ control ??= GetHoveredControl(Root);
- var control = Root.GetVisualsAt(point, x =>
+ if (control != null)
{
- if (x is AdornerLayer || !x.IsVisible) return false;
- if (!(x is IInputElement ie)) return true;
- return ie.IsHitTestVisible;
- })
- .FirstOrDefault();
+ vm.SelectControl(control);
+ }
+
+ break;
+ }
- if (control != null)
+ case RawInputModifiers.Control | RawInputModifiers.Alt when e.Key == Key.F:
{
- vm.SelectControl((IControl)control);
+ vm.FreezePopups = !vm.FreezePopups;
+
+ foreach (var popupRoot in GetPopupRoots(Root))
+ {
+ if (popupRoot.Parent is Popup popup)
+ {
+ if (vm.FreezePopups)
+ {
+ var lightDismissEnabledState = popup.SetValue(
+ Popup.IsLightDismissEnabledProperty,
+ !vm.FreezePopups,
+ BindingPriority.Animation);
+
+ if (lightDismissEnabledState != null)
+ {
+ _frozenPopupStates[popup] = lightDismissEnabledState;
+ }
+ }
+ else
+ {
+ //TODO Use Dictionary.Remove(Key, out Value) in netstandard 2.1
+ if (_frozenPopupStates.ContainsKey(popup))
+ {
+ _frozenPopupStates[popup].Dispose();
+ _frozenPopupStates.Remove(popup);
+ }
+ }
+ }
+ }
+
+ break;
}
- }
- else if (e.Modifiers == RawInputModifiers.Alt)
- {
- if (e.Key == Key.S || e.Key == Key.D)
+
+ case RawInputModifiers.Alt when e.Key == Key.S || e.Key == Key.D:
{
- var enable = e.Key == Key.S;
+ vm.EnableSnapshotStyles(e.Key == Key.S);
- vm.EnableSnapshotStyles(enable);
+ break;
}
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
index a5328716fc..bb661f7f4c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
index b63c49bd5a..01212b238d 100644
--- a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
+++ b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
@@ -1,8 +1,11 @@
+using System;
+
namespace Avalonia.VisualTree
{
///
/// Interface for controls that host their own separate visual tree, such as popups.
///
+ [Obsolete]
public interface IVisualTreeHost
{
///