diff --git a/src/JellyBox/MainPage.xaml b/src/JellyBox/MainPage.xaml index 5458501..cbac998 100644 --- a/src/JellyBox/MainPage.xaml +++ b/src/JellyBox/MainPage.xaml @@ -27,8 +27,10 @@ IsTabStop="False" PlaceholderText="Search movies and TV shows" Style="{StaticResource SearchAutoSuggestBox}" + UpdateTextOnSelect="False" ItemsSource="{x:Bind ViewModel.Search.Suggestions, Mode=OneWay}" TextChanged="SearchBox_TextChanged" + PreviewKeyDown="SearchBox_PreviewKeyDown" QuerySubmitted="SearchBox_QuerySubmitted" SuggestionChosen="SearchBox_SuggestionChosen" LostFocus="SearchBox_LostFocus" diff --git a/src/JellyBox/MainPage.xaml.cs b/src/JellyBox/MainPage.xaml.cs index e9059f9..57afd8c 100644 --- a/src/JellyBox/MainPage.xaml.cs +++ b/src/JellyBox/MainPage.xaml.cs @@ -15,7 +15,7 @@ namespace JellyBox; internal sealed partial class MainPage : Page { private FrameworkElement? _lastFocusedElement; - private bool _ignoreNextQuerySubmitted; + private int _suppressSearchSubmitEvents; private bool _suppressSearchTextSync; public MainPage() @@ -135,9 +135,26 @@ private void ContentFrameNavigated(object sender, NavigationEventArgs e) private void SearchBox_LostFocus(object sender, RoutedEventArgs e) { + // Keep the search box in the focus order while suggestions are open so the user + // can dismiss the OSK with B and navigate the list with the d-pad. + if (ViewModel.Search.Suggestions.Count > 0) + { + return; + } + SearchBox.IsTabStop = false; } + private void SearchBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (GamepadInput.IsBackKey(e.Key)) + { + // Dismissing the OSK can spuriously fire QuerySubmitted/SuggestionChosen with + // the first suggestion; swallow the next couple of submit events. + SuppressSearchSubmitEvents(2); + } + } + private void CloseNavigation(object sender, TappedRoutedEventArgs e) { ViewModel.CloseNavigationCommand.Execute(null); @@ -165,32 +182,45 @@ private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChan private void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { - if (_ignoreNextQuerySubmitted) + if (ConsumeSuppressedSearchSubmitEvent()) { - _ignoreNextQuerySubmitted = false; - return; - } - - if (args.ChosenSuggestion is SearchSuggestion suggestion) - { - OpenSearchSuggestion(suggestion); return; } + // ChosenSuggestion is populated when the OSK is dismissed with B on Xbox, not only + // on deliberate picks. Explicit suggestion selection goes through SuggestionChosen. ViewModel.Search.SubmitQuery(args.QueryText); } private void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { + if (ConsumeSuppressedSearchSubmitEvent()) + { + return; + } + if (args.SelectedItem is SearchSuggestion suggestion) { OpenSearchSuggestion(suggestion); } } + private void SuppressSearchSubmitEvents(int count) => _suppressSearchSubmitEvents = count; + + private bool ConsumeSuppressedSearchSubmitEvent() + { + if (_suppressSearchSubmitEvents <= 0) + { + return false; + } + + _suppressSearchSubmitEvents--; + return true; + } + private void OpenSearchSuggestion(SearchSuggestion suggestion) { - _ignoreNextQuerySubmitted = true; + SuppressSearchSubmitEvents(1); _suppressSearchTextSync = true; try