Fix details button
Re-worked behavior logic to ensure the button appears and disappears appropriately
Fixes
Fixes
MODIFY
WalletWasabi.Fluent/Behaviors/HistoryItemDetailsBehavior.cs
WalletWasabi.Fluent/Behaviors/HistoryItemDetailsBehavior.cs
@@ -4,108 +4,92 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
-using Avalonia.Threading;
+using ReactiveUI;
using WalletWasabi.Fluent.Controls;
+using WalletWasabi.Fluent.Extensions;
namespace WalletWasabi.Fluent.Behaviors;
public class HistoryItemDetailsBehavior : DisposingBehavior<TreeDataGridRow>
{
- private TreeDataGridItemDetailsAdorner? _itemDetailsAdorner;
- private IDisposable? _currentAdornerEvents;
+ private TreeDataGridItemDetailsAdorner _adorner;
- protected override void OnAttached(CompositeDisposable disposables)
- {
- if (AssociatedObject is not null)
- {
- Observable.FromEventPattern(AssociatedObject, nameof(AssociatedObject.DetachedFromVisualTree))
- .Subscribe(_ => Remove())
- .DisposeWith(disposables);
-
- AssociatedObject
- .GetObservable(InputElement.IsPointerOverProperty)
- .Subscribe(_ =>
- {
- if (AssociatedObject.IsPointerOver)
+ protected override void OnDetaching()
{
- AddAdorner(AssociatedObject);
+ base.OnDetaching();
+ RemoveAdorner();
}
- else
+
+ protected override void OnAttached(CompositeDisposable disposables)
{
- CheckIfShouldRemove();
- }
- })
+ Observable.FromEventPattern(AssociatedObject, nameof(AssociatedObject.Initialized))
+ .Subscribe(_ => AssociatedObjectOnInitialized())
.DisposeWith(disposables);
}
- }
- private void CheckIfShouldRemove()
- {
- Dispatcher.UIThread.Post(() =>
+ private static IObservable<bool> GetIsAdornerVisible(InputElement associatedObject, InputElement adorner)
{
- if (AssociatedObject is not null && _itemDetailsAdorner is not null && !AssociatedObject.IsPointerOver && !_itemDetailsAdorner.IsPointerOver)
- {
- RemoveAdorner(AssociatedObject);
- }
- });
+ var isPointerOverAdorner = adorner.GetObservable(InputElement.IsPointerOverProperty);
+ var isPointerOverAssociatedObject = associatedObject.GetObservable(InputElement.IsPointerOverProperty);
+
+ var delay = TimeSpan.FromSeconds(0.05);
+
+ var overAssociated = isPointerOverAssociatedObject.DelayFalse(delay);
+ var overAdorner = isPointerOverAdorner.DelayFalse(delay);
+
+ return overAssociated
+ .CombineLatest(overAdorner, (isOverAssociated, isOverAdorner) => isOverAssociated || isOverAdorner)
+ .DistinctUntilChanged();
}
- private void Remove()
- {
- if (AssociatedObject is not null)
+ private void AssociatedObjectOnInitialized()
{
- RemoveAdorner(AssociatedObject);
- }
+ var adorner = AddAdorner(AssociatedObject);
+ _adorner = adorner;
- _currentAdornerEvents?.Dispose();
- _currentAdornerEvents = null;
+ var isAdornerVisible = GetIsAdornerVisible(AssociatedObject, adorner);
+
+ isAdornerVisible
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Subscribe(isVisible => _adorner.IsVisible = isVisible);
}
- protected override void OnDetaching()
+ private AdornerCanvas? GetAdornerLayer()
{
- Remove();
+ if (AssociatedObject is null)
+ {
+ return null;
+ }
- base.OnDetaching();
+ return AdornerCanvas.GetAdornerLayer(AssociatedObject);
}
- private void AddAdorner(TreeDataGridRow row)
+ private void RemoveAdorner()
{
- _currentAdornerEvents?.Dispose();
- _currentAdornerEvents = null;
+ var layer = GetAdornerLayer();
- var layer = AdornerCanvas.GetAdornerLayer(row);
- if (layer is null || _itemDetailsAdorner is not null)
+ if (layer is null)
{
return;
}
- _itemDetailsAdorner = new TreeDataGridItemDetailsAdorner
- {
- [AdornerCanvas.AdornedElementProperty] = row,
- [AdornerCanvas.IsClipEnabledProperty] = false,
- Row = row
- };
-
- _currentAdornerEvents = _itemDetailsAdorner.GetObservable(InputElement.IsPointerOverProperty)
- .Subscribe(_ => CheckIfShouldRemove());
-
- ((ISetLogicalParent)_itemDetailsAdorner).SetParent(row);
- layer.Children.Add(_itemDetailsAdorner);
+ layer.Children.Remove(_adorner);
}
- private void RemoveAdorner(TreeDataGridRow row)
+ private TreeDataGridItemDetailsAdorner AddAdorner(TreeDataGridRow? to)
{
- _currentAdornerEvents?.Dispose();
- _currentAdornerEvents = null;
+ var layer = GetAdornerLayer();
- var layer = AdornerCanvas.GetAdornerLayer(row);
- if (layer is null || _itemDetailsAdorner is null)
+ var adorner = new TreeDataGridItemDetailsAdorner
{
- return;
- }
+ [AdornerCanvas.AdornedElementProperty] = to,
+ [AdornerCanvas.IsClipEnabledProperty] = false,
+ Row = to
+ };
- layer.Children.Remove(_itemDetailsAdorner);
- ((ISetLogicalParent)_itemDetailsAdorner).SetParent(null);
- _itemDetailsAdorner = null;
+ var setLogicalParent = (ISetLogicalParent) adorner;
+ setLogicalParent.SetParent(to);
+ layer.Children.Add(adorner);
+ return adorner;
}
}
\ No newline at end of file

ADD
WalletWasabi.Fluent/Extensions/ObservableMixin.cs
+using System.Reactive.Linq;
+
+namespace WalletWasabi.Fluent.Extensions;
+
+public static class ObservableMixin
+{
+ public static IObservable<T> DelayWhen<T>(this IObservable<T> observable, Func<T, bool> filter, TimeSpan ts)
+ {
+ return observable
+ .Select(x => filter(x) ? Observable.Return(x).Delay(ts) : Observable.Return(x)).Concat();
+ }
+
+ public static IObservable<bool> DelayFalse(this IObservable<bool> observable, TimeSpan ts)
+ {
+ return DelayWhen(observable, b => !b, ts);
+ }
+}
\ No newline at end of file

Fix details button

Re-worked behavior logic to ensure the button appears and disappears appropriately

Fixes #6840
Fixes #6928