From bfdd66ac5e89fbf3e12488492ea99cc9d5546fdd Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Dec 2022 23:25:36 -0500 Subject: [PATCH] Remove System.Reactive from Diagnostics package as well --- src/Avalonia.Base/Reactive/Observable.cs | 9 ++++ .../Reactive/SerialDisposableValue.cs | 35 ++++++++++++ .../Avalonia.Diagnostics.csproj | 1 - .../Diagnostics/DevTools.cs | 17 +++--- .../Diagnostics/ViewModels/EventTreeNode.cs | 1 + .../Diagnostics/ViewModels/LogicalTreeNode.cs | 2 +- .../Diagnostics/ViewModels/MainViewModel.cs | 14 ++--- .../Diagnostics/ViewModels/TreeNode.cs | 18 ++----- .../Diagnostics/ViewModels/VisualTreeNode.cs | 54 +++++++++++-------- .../Views/LayoutExplorerView.axaml.cs | 1 + .../Diagnostics/Views/MainWindow.xaml.cs | 30 ++++++----- 11 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 src/Avalonia.Base/Reactive/SerialDisposableValue.cs diff --git a/src/Avalonia.Base/Reactive/Observable.cs b/src/Avalonia.Base/Reactive/Observable.cs index 5191efa910..1009829429 100644 --- a/src/Avalonia.Base/Reactive/Observable.cs +++ b/src/Avalonia.Base/Reactive/Observable.cs @@ -44,6 +44,15 @@ internal static class Observable }, obs.OnError, obs.OnCompleted)); }); } + + public static IObservable StartWith(this IObservable source, TSource value) + { + return Create(obs => + { + obs.OnNext(value); + return source.Subscribe(obs); + }); + } public static IObservable Where(this IObservable source, Func predicate) { diff --git a/src/Avalonia.Base/Reactive/SerialDisposableValue.cs b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs new file mode 100644 index 0000000000..9eaf6343bf --- /dev/null +++ b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive; + +/// +/// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. +/// +internal sealed class SerialDisposableValue : IDisposable +{ + private IDisposable? _current; + private bool _disposed; + + public IDisposable? Disposable + { + get => _current; + set + { + _current?.Dispose(); + _current = value; + + if (_disposed) + { + _current?.Dispose(); + _current = null; + } + } + } + + public void Dispose() + { + _disposed = true; + _current?.Dispose(); + } +} diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index fe694b5730..65d1bea298 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 9b4e13959b..e8cdcfa37b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Views; using Avalonia.Input; @@ -72,7 +71,11 @@ namespace Avalonia.Diagnostics { throw new ArgumentNullException(nameof(application)); } - var result = Disposable.Empty; + + var openedDisposable = new SerialDisposableValue(); + var result = new CompositeDisposable(2); + result.Add(openedDisposable); + // Skip if call on Design Mode if (!Avalonia.Controls.Design.IsDesignMode && !s_attachedToApplication) @@ -90,13 +93,15 @@ namespace Avalonia.Diagnostics { s_attachedToApplication = true; - ObservableExtensions.Subscribe(application.InputManager.PreProcess.OfType(), e => + result.Add(application.InputManager.PreProcess.Subscribe(e => { - if (options.Gesture.Matches(e)) + if (e is RawKeyEventArgs keyEventArgs + && keyEventArgs.Type == RawKeyEventType.KeyUp + && options.Gesture.Matches(keyEventArgs)) { - result = Open(application, options, owner); + openedDisposable.Disposable = Open(application, options, owner); } - }); + })); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index 7da43a51b7..0140281d50 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -4,6 +4,7 @@ using Avalonia.Diagnostics.Views; using Avalonia.Interactivity; using Avalonia.Threading; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index 7c6246701a..3048431af9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -1,10 +1,10 @@ using System; -using System.Reactive.Disposables; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 52535aa991..3870cad7c5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,11 +1,11 @@ using System; using System.ComponentModel; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Metadata; using Avalonia.Threading; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -50,15 +50,15 @@ namespace Avalonia.Diagnostics.ViewModels } else { -#nullable disable - _pointerOverSubscription = InputManager.Instance.PreProcess - .OfType() + _pointerOverSubscription = InputManager.Instance!.PreProcess .Subscribe(e => { - PointerOverRoot = e.Root; - PointerOverElement = e.Root.InputHitTest(e.Position); + if (e is Input.Raw.RawPointerEventArgs pointerEventArgs) + { + PointerOverRoot = pointerEventArgs.Root; + PointerOverElement = pointerEventArgs.Root.InputHitTest(pointerEventArgs.Position); + } }); -#nullable restore } Console = new ConsoleViewModel(UpdateConsoleContext); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 65bfd7fc36..aafaa096e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Specialized; -using System.Reactive; -using System.Reactive.Linq; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -28,18 +28,8 @@ namespace Avalonia.Diagnostics.ViewModels { ElementName = control.Name; - var removed = Observable.FromEventPattern( - x => control.DetachedFromLogicalTree += x, - x => control.DetachedFromLogicalTree -= x); - var classesChanged = Observable.FromEventPattern< - NotifyCollectionChangedEventHandler, - NotifyCollectionChangedEventArgs>( - x => control.Classes.CollectionChanged += x, - x => control.Classes.CollectionChanged -= x) - .TakeUntil(removed); - - _classesSubscription = classesChanged.Select(_ => Unit.Default) - .StartWith(Unit.Default) + _classesSubscription = ((IObservable)control.Classes.GetWeakCollectionChangedObservable()) + .StartWith(null) .Subscribe(_ => { if (control.Classes.Count > 0) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index e44b852372..f9fb0d18ef 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,12 +1,11 @@ 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; +using Avalonia.Reactive; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; @@ -61,9 +60,12 @@ namespace Avalonia.Diagnostics.ViewModels IPopupHostProvider popupHostProvider, string? providerName = null) { - return Observable.FromEvent( - x => popupHostProvider.PopupHostChanged += x, - x => popupHostProvider.PopupHostChanged -= x) + return Observable.Create(observer => + { + void Handler(IPopupHost? args) => observer.OnNext(args); + popupHostProvider.PopupHostChanged += Handler; + return Disposable.Create(() => popupHostProvider.PopupHostChanged -= Handler); + }) .StartWith(popupHostProvider.PopupHost) .Select(popupHost => { @@ -80,29 +82,39 @@ namespace Avalonia.Diagnostics.ViewModels { Popup p => GetPopupHostObservable(p), Control c => Observable.CombineLatest( - c.GetObservable(Control.ContextFlyoutProperty), - c.GetObservable(Control.ContextMenuProperty), - c.GetObservable(FlyoutBase.AttachedFlyoutProperty), - c.GetObservable(ToolTipDiagnostics.ToolTipProperty), - c.GetObservable(Button.FlyoutProperty), - (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip, ButtonFlyout) => + new IObservable[] + { + c.GetObservable(Control.ContextFlyoutProperty), + c.GetObservable(Control.ContextMenuProperty), + c.GetObservable(FlyoutBase.AttachedFlyoutProperty), + c.GetObservable(ToolTipDiagnostics.ToolTipProperty), + c.GetObservable(Button.FlyoutProperty) + }) + .Select( + items => { - if (ContextMenu != null) + var contextFlyout = items[0]; + var contextMenu = (ContextMenu?)items[1]; + var attachedFlyout = items[2]; + var toolTip = items[3]; + var buttonFlyout = items[4]; + + 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)); + return Observable.Return(new PopupRoot(contextMenu)); - if (ContextFlyout != null) - return GetPopupHostObservable(ContextFlyout, "ContextFlyout"); + if (contextFlyout != null) + return GetPopupHostObservable(contextFlyout, "ContextFlyout"); - if (AttachedFlyout != null) - return GetPopupHostObservable(AttachedFlyout, "AttachedFlyout"); + if (attachedFlyout != null) + return GetPopupHostObservable(attachedFlyout, "AttachedFlyout"); - if (ToolTip != null) - return GetPopupHostObservable(ToolTip, "ToolTip"); + if (toolTip != null) + return GetPopupHostObservable(toolTip, "ToolTip"); - if (ButtonFlyout != null) - return GetPopupHostObservable(ButtonFlyout, "Flyout"); + if (buttonFlyout != null) + return GetPopupHostObservable(buttonFlyout, "Flyout"); return Observable.Return(null); }) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs index c81e3cadf4..56d8737d79 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Diagnostics.Controls; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 13b8cf5e8a..dbc4c98f78 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; @@ -13,13 +12,13 @@ using Avalonia.Markup.Xaml; using Avalonia.Styling; using Avalonia.Themes.Simple; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { internal class MainWindow : Window, IStyleHost { - private readonly IDisposable? _keySubscription; - private readonly IDisposable? _pointerSubscription; + private readonly IDisposable? _inputSubscription; private readonly Dictionary _frozenPopupStates; private AvaloniaObject? _root; private PixelPoint _lastPointerPosition; @@ -33,15 +32,19 @@ namespace Avalonia.Diagnostics.Views if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme) Theme = windowTheme; - _keySubscription = InputManager.Instance?.Process - .OfType() - .Where(x => x.Type == RawKeyEventType.KeyDown) - .Subscribe(RawKeyDown); - _pointerSubscription = InputManager.Instance?.Process - .OfType() - .Subscribe(x => _lastPointerPosition = ((Visual)x.Root).PointToScreen(x.Position)); - - + _inputSubscription = InputManager.Instance?.Process + .Subscribe(x => + { + if (x is RawPointerEventArgs pointerEventArgs) + { + _lastPointerPosition = ((Visual)x.Root).PointToScreen(pointerEventArgs.Position); + } + else if (x is RawKeyEventArgs keyEventArgs && keyEventArgs.Type == RawKeyEventType.KeyDown) + { + RawKeyDown(keyEventArgs); + } + }); + _frozenPopupStates = new Dictionary(); EventHandler? lh = default; @@ -94,8 +97,7 @@ namespace Avalonia.Diagnostics.Views protected override void OnClosed(EventArgs e) { base.OnClosed(e); - _keySubscription?.Dispose(); - _pointerSubscription?.Dispose(); + _inputSubscription?.Dispose(); foreach (var state in _frozenPopupStates) {