Rescue Dan's SearchBarBehavior (and improve it)
ADD
WalletWasabi.Fluent/Behaviors/SearchBarBehavior.cs
+using System.Reactive.Disposables;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
+namespace WalletWasabi.Fluent.Behaviors;
+
+public class SearchBarBehavior : AttachedToVisualTreeBehavior<UserControl>
+{
+ public static readonly StyledProperty<TextBox?> SearchBoxProperty =
+ AvaloniaProperty.Register<SearchBarBehavior, TextBox?>(nameof(SearchBox));
+
+ public static readonly StyledProperty<UserControl?> SearchPanelProperty =
+ AvaloniaProperty.Register<SearchBarBehavior, UserControl?>(nameof(SearchPanel));
+
+ public static readonly StyledProperty<bool> IsSearchPanelOpenProperty =
+ AvaloniaProperty.Register<SearchBarBehavior, bool>(nameof(SearchBox));
+
+ private TopLevel? _topLevel;
+
+ [ResolveByName]
+ public TextBox? SearchBox
+ {
+ get => GetValue(SearchBoxProperty);
+ set => SetValue(SearchBoxProperty, value);
+ }
+
+ [ResolveByName]
+ public UserControl? SearchPanel
+ {
+ get => GetValue(SearchPanelProperty);
+ set => SetValue(SearchPanelProperty, value);
+ }
+
+
+ public bool IsSearchPanelOpen
+ {
+ get => GetValue(IsSearchPanelOpenProperty);
+ set => SetValue(IsSearchPanelOpenProperty, value);
+ }
+
+ protected override void OnAttachedToVisualTree(CompositeDisposable disposables)
+ {
+ this.GetObservable(IsSearchPanelOpenProperty)
+ .Subscribe(ToggleFlyoutOpen);
+
+ if (AssociatedObject is null)
+ {
+ return;
+ }
+
+ if (AssociatedObject.GetVisualRoot() is TopLevel topLevel)
+ {
+ _topLevel = topLevel;
+
+ topLevel.AddHandler(InputElement.PointerPressedEvent, OnTopLevelPointerPressed, RoutingStrategies.Tunnel);
+ }
+
+ if (SearchBox is { } && SearchPanel is { })
+ {
+ SearchBox.GotFocus += SearchBoxOnGotFocus;
+ AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
+ }
+ }
+
+ protected override void OnDetaching()
+ {
+ base.OnDetaching();
+
+ if (_topLevel is { })
+ {
+ _topLevel.RemoveHandler(InputElement.PointerPressedEvent, OnTopLevelPointerPressed);
+
+ _topLevel = null;
+ }
+
+ if (SearchBox is { })
+ {
+ SearchBox.GotFocus -= SearchBoxOnGotFocus;
+ }
+
+ if (AssociatedObject is { })
+ {
+ AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;
+ }
+ }
+
+ private void ToggleFlyoutOpen(bool isOpen)
+ {
+ if (isOpen)
+ {
+ ShowFlyout();
+ }
+ else
+ {
+ HideFlyout();
+ }
+ }
+
+ private void SearchBoxOnGotFocus(object? sender, GotFocusEventArgs e)
+ {
+ ShowFlyout();
+ }
+
+ private void ShowFlyout()
+ {
+ FlyoutBase.ShowAttachedFlyout(AssociatedObject);
+ IsSearchPanelOpen = true;
+ }
+
+ private void AssociatedObjectOnLostFocus(object? sender, RoutedEventArgs e)
+ {
+ if (AssociatedObject is { } && SearchPanel is { })
+ {
+ if (!AssociatedObject.IsKeyboardFocusWithin && !SearchPanel.IsKeyboardFocusWithin)
+ {
+ HideFlyout();
+ }
+ }
+ }
+
+ private void HideFlyout()
+ {
+ FlyoutBase.GetAttachedFlyout(AssociatedObject).Hide();
+ IsSearchPanelOpen = false;
+ }
+
+ private void OnTopLevelPointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (AssociatedObject is { } && !AssociatedObject.IsPointerOver)
+ {
+ if (ReferenceEquals(FocusManager.Instance?.Current, SearchBox))
+ {
+ FocusManager.Instance?.Focus(null);
+ }
+ }
+ }
+}
\ No newline at end of file

DELETE
WalletWasabi.Fluent/Behaviors/ShowAssociatedFlyoutBehavior.cs
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.Xaml.Interactivity;
-
-namespace WalletWasabi.Fluent.Behaviors;
-
-public class ShowAssociatedFlyoutBehavior : Behavior<Control>
-{
- public static readonly StyledProperty<bool> IsSearchPanelOpenProperty =
- AvaloniaProperty.Register<ShowAssociatedFlyoutBehavior, bool>(nameof(IsSearchPanelOpen));
-
- private IDisposable? _isOpenSyncer;
- private IDisposable? _flyoutToggler;
-
- public bool IsSearchPanelOpen
- {
- get => GetValue(IsSearchPanelOpenProperty);
- set => SetValue(IsSearchPanelOpenProperty, value);
- }
-
- protected override void OnAttachedToVisualTree()
- {
- _isOpenSyncer = FlyoutBase.GetAttachedFlyout(AssociatedObject)
- .GetObservable(FlyoutBase.IsOpenProperty)
- .Subscribe(b => IsSearchPanelOpen = b);
-
- _flyoutToggler = this.GetObservable(IsSearchPanelOpenProperty).Subscribe(isOpen =>
- {
- if (isOpen)
- {
- FlyoutBase.ShowAttachedFlyout(AssociatedObject);
- }
- else
- {
- FlyoutBase.GetAttachedFlyout(AssociatedObject).Hide();
- }
- });
- }
-
- protected override void OnDetachedFromVisualTree()
- {
- _flyoutToggler?.Dispose();
- _flyoutToggler?.Dispose();
-
- base.OnDetachedFromVisualTree();
- }
-}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/Views/SearchBar/SearchBar.axaml
WalletWasabi.Fluent/Views/SearchBar/SearchBar.axaml
<TextBox.InnerRightContent>
<PathIcon Data="{StaticResource search_regular}" Margin="10 0" />
</TextBox.InnerRightContent>
- <i:Interaction.Behaviors>
- <RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.PointerPressedEvent}" RoutingStrategies="Tunnel">
- <ia:InvokeCommandAction Command="{Binding ShowListCommand}" />
- </RoutedEventTriggerBehavior>
- <RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.GotFocusEvent}">
- <ia:InvokeCommandAction Command="{Binding ShowListCommand}" />
- </RoutedEventTriggerBehavior>
- </i:Interaction.Behaviors>
</TextBox>
<FlyoutBase.AttachedFlyout>
</FlyoutBase.AttachedFlyout>
<i:Interaction.Behaviors>
- <behaviors:ShowAssociatedFlyoutBehavior IsSearchPanelOpen="{Binding IsSearchListVisible, Mode=TwoWay}"/>
+ <behaviors:SearchBarBehavior SearchBox="SearchBox" SearchPanel="SearchBarDropdown" IsSearchPanelOpen="{Binding IsSearchListVisible, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</UserControl>
\ No newline at end of file

Rescue Dan's SearchBarBehavior (and improve it)