Skip to content

Code Quality: Improved Omnibar 14 #17248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Files.App.Controls/Omnibar/EventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public record class OmnibarSuggestionChosenEventArgs(OmnibarMode Mode, object Se
public record class OmnibarTextChangedEventArgs(OmnibarMode Mode, OmnibarTextChangeReason Reason);

public record class OmnibarModeChangedEventArgs(OmnibarMode? OldMode, OmnibarMode NewMode);

public record class OmnibarIsFocusedChangedEventArgs(bool IsFocused);
}
3 changes: 3 additions & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
GlobalHelper.WriteDebugStringForOmnibar("The TextBox got the focus.");

IsFocused = true;
IsFocusedChanged?.Invoke(this, new(IsFocused));

_textBox.SelectAll();
}

Expand All @@ -52,6 +54,7 @@ private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e)
GlobalHelper.WriteDebugStringForOmnibar("The TextBox lost the focus.");

IsFocused = false;
IsFocusedChanged?.Invoke(this, new(IsFocused));

// Reset to the default mode when Omnibar loses focus
CurrentSelectedMode = Modes?.FirstOrDefault();
Expand Down
1 change: 1 addition & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public partial class Omnibar : Control
public event TypedEventHandler<Omnibar, OmnibarSuggestionChosenEventArgs>? SuggestionChosen;
public event TypedEventHandler<Omnibar, OmnibarTextChangedEventArgs>? TextChanged;
public event TypedEventHandler<Omnibar, OmnibarModeChangedEventArgs>? ModeChanged;
public event TypedEventHandler<Omnibar, OmnibarIsFocusedChangedEventArgs> IsFocusedChanged;

// Constructor

Expand Down
1 change: 1 addition & 0 deletions src/Files.App.Controls/Omnibar/Omnibar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
x:Name="PART_ModeButton"
Width="{StaticResource OmnibarModeDefaultClickAreaWidth}"
Margin="1"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0"
Expand Down
44 changes: 40 additions & 4 deletions src/Files.App/UserControls/NavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@
Grid.Column="1"
x:Load="{x:Bind ViewModel.EnableOmnibar, Mode=OneWay}"
CurrentSelectedModeName="{x:Bind ViewModel.OmnibarCurrentSelectedModeName, Mode=TwoWay}"
IsFocused="{x:Bind ViewModel.IsOmnibarFocused, Mode=TwoWay}"
IsFocusedChanged="Omnibar_IsFocusedChanged"
ModeChanged="Omnibar_ModeChanged"
PreviewKeyDown="Omnibar_PreviewKeyDown"
QuerySubmitted="Omnibar_QuerySubmitted"
Expand Down Expand Up @@ -412,9 +412,9 @@
UpdateTextOnSelect="False">
<controls:OmnibarMode.ItemTemplate>
<DataTemplate x:DataType="dataitems:NavigationBarSuggestionItem">
<Grid ColumnSpacing="12">
<Grid Padding="0,0,4,0" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
Expand Down Expand Up @@ -458,8 +458,44 @@
IconOnInactive="{controls:ThemedIconMarkup Style={StaticResource App.ThemedIcons.Omnibar.Search}, IconType=Outline}"
IsAutoFocusEnabled="True"
IsEnabled="False"
ItemsSource="{x:Bind ViewModel.OmnibarSearchModeSuggestionItems, Mode=OneWay}"
ModeName="{x:Bind Commands.Search.LabelWithHotKey, Mode=OneWay}"
PlaceholderText="{helpers:ResourceString Name=OmnibarSearchModeTextPlaceholder}" />
PlaceholderText="{helpers:ResourceString Name=OmnibarSearchModeTextPlaceholder}"
Text="{x:Bind ViewModel.OmnibarSearchModeText, Mode=TwoWay}"
UpdateTextOnSelect="False">
<controls:OmnibarMode.ItemTemplate>
<DataTemplate x:DataType="datamodels:SuggestionModel">
<Grid ColumnSpacing="8" Tag="{x:Bind ItemPath}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Column="0"
Width="16"
Height="16"
Tag="ItemImage">
<Border x:Name="IconArea" x:Load="{x:Bind LoadFileIcon, Mode=OneWay}">
<Image
Width="16"
Height="16"
Source="{x:Bind FileImage, Mode=OneWay}"
Stretch="Uniform" />
</Border>
<FontIcon
x:Name="EmptyIconGlyph"
x:Load="{x:Bind NeedsPlaceholderGlyph, Mode=OneWay}"
FontSize="14"
Glyph="{x:Bind IsRecentSearch, Mode=OneTime, Converter={StaticResource SearchSuggestionGlyphConverter}}" />
</Grid>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</controls:OmnibarMode.ItemTemplate>
</controls:OmnibarMode>

</controls:Omnibar>

Expand Down
61 changes: 58 additions & 3 deletions src/Files.App/UserControls/NavigationToolbar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,66 @@ private void BreadcrumbBar_ItemDropDownFlyoutClosed(object sender, BreadcrumbBar
e.Flyout.Items.Clear();
}

private void Omnibar_ModeChanged(object sender, OmnibarModeChangedEventArgs e)
private async void Omnibar_ModeChanged(object sender, OmnibarModeChangedEventArgs e)
{
// Reset the command palette text when switching modes
if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode)
if (e.NewMode == OmnibarPathMode)
{
ViewModel.PathText = string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory)
? Constants.UserEnvironmentPaths.HomePath
: ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory;

await DispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
await ViewModel.PopulateOmnibarSuggestionsForPathMode();
});
}
else if (e.NewMode == OmnibarCommandPaletteMode)
{
ViewModel.OmnibarCommandPaletteModeText = string.Empty;

await DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
ViewModel.PopulateOmnibarSuggestionsForCommandPaletteMode();
});
}
else if (e.NewMode == OmnibarSearchMode)
{

}
}

private async void Omnibar_IsFocusedChanged(Omnibar sender, OmnibarIsFocusedChangedEventArgs args)
{
if (args.IsFocused)
{
if (Omnibar.CurrentSelectedMode == OmnibarPathMode)
{
ViewModel.PathText = string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory)
? Constants.UserEnvironmentPaths.HomePath
: ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory;

await DispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
await ViewModel.PopulateOmnibarSuggestionsForPathMode();
});
}
else if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode)
{
ViewModel.OmnibarCommandPaletteModeText = string.Empty;

if (ViewModel.OmnibarCommandPaletteModeSuggestionItems.Count is 0)
{
await DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
ViewModel.PopulateOmnibarSuggestionsForCommandPaletteMode();
});
}
}
else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode)
{

}
}
}

private async void Omnibar_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
Expand Down
118 changes: 29 additions & 89 deletions src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public sealed partial class NavigationToolbarViewModel : ObservableObject, IAddr

internal ObservableCollection<NavigationBarSuggestionItem> OmnibarCommandPaletteModeSuggestionItems { get; } = [];

internal ObservableCollection<SuggestionModel> OmnibarSearchModeSuggestionItems { get; } = [];

public bool IsSingleItemOverride { get; set; }

public bool SearchHasFocus { get; private set; }
Expand Down Expand Up @@ -231,66 +233,14 @@ public string? PathText
private string? _OmnibarCommandPaletteModeText;
public string? OmnibarCommandPaletteModeText { get => _OmnibarCommandPaletteModeText; set => SetProperty(ref _OmnibarCommandPaletteModeText, value); }

private bool _IsOmnibarFocused;
public bool IsOmnibarFocused
{
get => _IsOmnibarFocused;
set
{
// NOTE: Don't call ObservableObject.SetProperty() here since we don't want to change focus logic outside of the control.

_IsOmnibarFocused = value;

if (value)
{
switch (OmnibarCurrentSelectedModeName)
{
case OmnibarPathModeName:
PathText =
string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory)
? Constants.UserEnvironmentPaths.HomePath
: ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory;
_ = PopulateOmnibarSuggestionsForPathMode();
break;
case OmnibarPaletteModeName:
PopulateOmnibarSuggestionsForCommandPaletteMode();
break;
case OmnibarSearchModeName:
break;
default:
break;
}
}
}
}
private string? _OmnibarSearchModeText;
public string? OmnibarSearchModeText { get => _OmnibarSearchModeText; set => SetProperty(ref _OmnibarSearchModeText, value); }

private string _OmnibarCurrentSelectedModeName = OmnibarPathModeName;
public string OmnibarCurrentSelectedModeName
{
get => _OmnibarCurrentSelectedModeName;
set
{
if (SetProperty(ref _OmnibarCurrentSelectedModeName, value) && IsOmnibarFocused)
{
switch (value)
{
case OmnibarPathModeName:
PathText =
string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory)
? Constants.UserEnvironmentPaths.HomePath
: ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory;
_ = PopulateOmnibarSuggestionsForPathMode();
break;
case OmnibarPaletteModeName:
PopulateOmnibarSuggestionsForCommandPaletteMode();
break;
case OmnibarSearchModeName:
break;
default:
break;
}
}
}
set => SetProperty(ref _OmnibarCurrentSelectedModeName, value);
}

private CurrentInstanceViewModel _InstanceViewModel;
Expand Down Expand Up @@ -1100,8 +1050,6 @@ private static async Task<bool> LaunchApplicationFromPath(string currentInput, s

public async Task PopulateOmnibarSuggestionsForPathMode()
{
PathModeSuggestionItems.Clear();

var result = await SafetyExtensions.IgnoreExceptions((Func<Task<bool>>)(async () =>
{
List<OmnibarPathModeSuggestionModel>? newSuggestions = [];
Expand Down Expand Up @@ -1200,49 +1148,41 @@ void AddNoResultsItem()

public void PopulateOmnibarSuggestionsForCommandPaletteMode()
{
OmnibarCommandPaletteModeText ??= string.Empty;
OmnibarCommandPaletteModeSuggestionItems.Clear();

if (ContentPageContext.SelectedItems.Count == 1 && ContentPageContext.SelectedItem is not null && !ContentPageContext.SelectedItem.IsFolder)
{
var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();

dispatcherQueue.TryEnqueue(() =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that DispatcherQueue is required for actions to work properly. Unfortunately, we can't do any further testing in this area right now because the experimental API doesn't seem to work in the latest Insider build.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look at the L439 of NavigationToolbar.xaml.cs, I've wrapped the whole method with it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's missing from the Omnibar_TextChanged event. To prevent this from being missed in the future, it would be safer to revert this change.

try
{
try
var selectedItemPath = ContentPageContext.SelectedItem.ItemPath;
var fileActionEntity = ActionManager.Instance.EntityFactory.CreateFileEntity(selectedItemPath);
var actions = ActionManager.Instance.ActionRuntime.ActionCatalog.GetActionsForInputs(new[] { fileActionEntity });

foreach (var action in actions.Where(a => a.Definition.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)))
{
var selectedItemPath = ContentPageContext.SelectedItem.ItemPath;
var fileActionEntity = ActionManager.Instance.EntityFactory.CreateFileEntity(selectedItemPath);
var actions = ActionManager.Instance.ActionRuntime.ActionCatalog.GetActionsForInputs(new[] { fileActionEntity });
var newItem = new NavigationBarSuggestionItem
{
PrimaryDisplay = action.Definition.Description,
SearchText = OmnibarCommandPaletteModeText,
ActionInstance = action
};

foreach (var action in actions.Where(a => a.Definition.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)))
if (Uri.TryCreate(action.Definition.IconFullPath, UriKind.RelativeOrAbsolute, out Uri? validUri))
{
var newItem = new NavigationBarSuggestionItem
try
{
PrimaryDisplay = action.Definition.Description,
SearchText = OmnibarCommandPaletteModeText,
ActionInstance = action
};

if (Uri.TryCreate(action.Definition.IconFullPath, UriKind.RelativeOrAbsolute, out Uri? validUri))
newItem.ActionIconSource = new BitmapImage(validUri);
}
catch (Exception)
{
try
{
newItem.ActionIconSource = new BitmapImage(validUri);
}
catch (Exception)
{
}
}

OmnibarCommandPaletteModeSuggestionItems.Add(newItem);
}

OmnibarCommandPaletteModeSuggestionItems.Add(newItem);
}
catch (Exception ex)
{
App.Logger.LogWarning(ex, ex.Message);
}
});
}
catch (Exception ex)
{
App.Logger.LogWarning(ex, ex.Message);
}
}

var suggestionItems = Commands
Expand Down
Loading