Skip to content

Code Quality: Brushed up Omnibar and BreadcrumbBar #16973

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 3 commits into from
Apr 4, 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
3 changes: 2 additions & 1 deletion Settings.XamlStyler
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"IndentWithTabs": true
"IndentWithTabs": true,
"NoNewLineMarkupExtensions": "x:Bind, Binding, controls:ThemedIconMarkup",
}
9 changes: 5 additions & 4 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public BreadcrumbBar()
{
DefaultStyleKey = typeof(BreadcrumbBar);

_itemsRepeaterLayout = new(this, 2d);
_itemsRepeaterLayout = new(this);
}

// Methods
Expand Down Expand Up @@ -87,12 +87,13 @@ internal protected virtual void RaiseItemDropDownFlyoutClosed(BreadcrumbBarItem

internal protected virtual void OnLayoutUpdated()
{
if (_itemsRepeater is null)
if (_itemsRepeater is null || (_itemsRepeaterLayout.IndexAfterEllipsis > _itemsRepeaterLayout.VisibleItemsCount && _isEllipsisRendered))
return;

if (_ellipsisBreadcrumbBarItem is not null && _isEllipsisRendered != _itemsRepeaterLayout.EllipsisIsRendered)
_ellipsisBreadcrumbBarItem.Visibility = _itemsRepeaterLayout.EllipsisIsRendered ? Visibility.Visible : Visibility.Collapsed;

_isEllipsisRendered = _itemsRepeaterLayout.EllipsisIsRendered;
if (_ellipsisBreadcrumbBarItem is not null)
_ellipsisBreadcrumbBarItem.Visibility = _isEllipsisRendered ? Visibility.Visible : Visibility.Collapsed;

for (int accessibilityIndex = 0, collectionIndex = _itemsRepeaterLayout.IndexAfterEllipsis;
accessibilityIndex < _itemsRepeaterLayout.VisibleItemsCount;
Expand Down
43 changes: 37 additions & 6 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:local="using:Files.App.Controls">

<x:Double x:Key="BreadcrumbBarHeight">32</x:Double>
<x:Double x:Key="BreadcrumbBarHeight">34</x:Double>
<x:Double x:Key="BreadcrumbBarMinWidth">120</x:Double>
<x:Double x:Key="BreadcrumbBarEllipsisFontSize">16</x:Double>

<Thickness x:Key="BreadcrumbBarChevronPadding">4,0</Thickness>
<Thickness x:Key="BreadcrumbBarItemPadding">8,0</Thickness>
<Thickness x:Key="BreadcrumbBarRootItemPadding">16,0,8,0</Thickness>
<Thickness x:Key="BreadcrumbBarItemMargin">2,0,0,0</Thickness>

<CornerRadius x:Key="BreadcrumbBarItemCornerRadius">2,2,2,2</CornerRadius>
<CornerRadius x:Key="BreadcrumbBarChevronCornerRaduis">2,2,2,2</CornerRadius>
Expand All @@ -21,21 +22,33 @@
<Style BasedOn="{StaticResource DefaultBreadcrumbBarItemStyle}" TargetType="local:BreadcrumbBarItem" />

<Style x:Key="DefaultBreadcrumbBarStyle" TargetType="local:BreadcrumbBar">

<Setter Property="MinWidth" Value="{StaticResource BreadcrumbBarMinWidth}" />

<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />

<Setter Property="AutomationProperties.LandmarkType" Value="Navigation" />

<Setter Property="IsTabStop" Value="False" />

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BreadcrumbBar">
<Grid
MinWidth="{TemplateBinding MinWidth}"
ColumnSpacing="2"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
TabFocusNavigation="Once"
XYFocusKeyboardNavigation="Enabled">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<local:BreadcrumbBarItem
Expand All @@ -50,6 +63,7 @@
<local:BreadcrumbBarItem
x:Name="PART_EllipsisBreadcrumbBarItem"
Grid.Column="1"
Margin="{StaticResource BreadcrumbBarItemMargin}"
AutomationProperties.AccessibilityView="Content"
IsEllipsis="True"
Visibility="Collapsed">
Expand All @@ -59,6 +73,8 @@
<ItemsRepeater
x:Name="PART_MainItemsRepeater"
Grid.Column="2"
Margin="{StaticResource BreadcrumbBarItemMargin}"
HorizontalAlignment="Left"
ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />

Expand All @@ -85,17 +101,17 @@
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />

<Setter Property="FocusVisualMargin" Value="1" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BreadcrumbBarItem">
<Grid
x:Name="PART_LayoutRoot"
ColumnSpacing="2"
TabFocusNavigation="Once"
XYFocusKeyboardNavigation="Enabled">
<Grid.ColumnDefinitions>
Expand All @@ -107,14 +123,21 @@
<Button
x:Name="PART_ItemContentButton"
Padding="{TemplateBinding Padding}"
VerticalAlignment="Stretch"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}"
UseSystemFocusVisuals="True">
<Button.Resources>
<ResourceDictionary>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="SubtleFillColorTertiaryBrush" />
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="SubtleFillColorSecondaryBrush" />
</ResourceDictionary>
</Button.Resources>

<FlyoutBase.AttachedFlyout>
<MenuFlyout
x:Name="PART_ItemEllipsisDropDownMenuFlyout"
Expand Down Expand Up @@ -151,15 +174,23 @@
<Button
x:Name="PART_ItemChevronButton"
Grid.Column="1"
Margin="{StaticResource BreadcrumbBarItemMargin}"
Padding="{StaticResource BreadcrumbBarChevronPadding}"
VerticalAlignment="Stretch"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Content"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{StaticResource BreadcrumbBarChevronCornerRaduis}"
Style="{StaticResource BreadcrumbBarItemChevronButtonStyle}"
UseSystemFocusVisuals="True">
<Button.Resources>
<ResourceDictionary>
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="SubtleFillColorTertiaryBrush" />
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="SubtleFillColorSecondaryBrush" />
</ResourceDictionary>
</Button.Resources>

<FlyoutBase.AttachedFlyout>
<MenuFlyout
x:Name="PART_ItemChevronDropDownMenuFlyout"
Expand Down
42 changes: 22 additions & 20 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,33 @@ protected override void OnApplyTemplate()

public void OnItemClicked()
{
if (_ownerRef is not null &&
_ownerRef.TryGetTarget(out var breadcrumbBar))
if (_ownerRef is null ||
!_ownerRef.TryGetTarget(out var breadcrumbBar))
return;

if (IsEllipsis)
{
if (IsEllipsis)
{
// Clear items in the ellipsis flyout
_itemEllipsisDropDownMenuFlyout.Items.Clear();
// Clear items in the ellipsis flyout
_itemEllipsisDropDownMenuFlyout.Items.Clear();

// Populate items in the ellipsis flyout
for (int index = 0; index < breadcrumbBar.IndexAfterEllipsis; index++)
// Populate items in the ellipsis flyout
for (int index = 0; index < breadcrumbBar.IndexAfterEllipsis; index++)
{
if (breadcrumbBar.TryGetElement(index, out var item) && item?.Content is string text)
{
if (breadcrumbBar.TryGetElement(index, out var item) && item?.Content is string text)
{
_itemEllipsisDropDownMenuFlyout.Items.Add(new MenuFlyoutItem() { Text = text });
}
var menuFlyoutItem = new MenuFlyoutItem() { Text = text };
_itemEllipsisDropDownMenuFlyout.Items.Add(menuFlyoutItem);
menuFlyoutItem.Click += (sender, e) => breadcrumbBar.RaiseItemClickedEvent(item);
}

// Open the ellipsis flyout
FlyoutBase.ShowAttachedFlyout(_itemContentButton);
}
else
{
// Fire a click event
breadcrumbBar.RaiseItemClickedEvent(this);
}

// Open the ellipsis flyout
FlyoutBase.ShowAttachedFlyout(_itemContentButton);
}
else
{
// Fire a click event
breadcrumbBar.RaiseItemClickedEvent(this);
}
}

Expand Down
11 changes: 5 additions & 6 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public partial class BreadcrumbBarLayout : NonVirtualizingLayout
// Fields

private readonly WeakReference<BreadcrumbBar>? _ownerRef;
private readonly double _spacing = 0d;

private Size _availableSize;
private BreadcrumbBarItem? _ellipsisButton = null;
Expand All @@ -24,10 +23,9 @@ public partial class BreadcrumbBarLayout : NonVirtualizingLayout
public int IndexAfterEllipsis { get; private set; }
public int VisibleItemsCount { get; private set; }

public BreadcrumbBarLayout(BreadcrumbBar breadcrumb, double spacing)
public BreadcrumbBarLayout(BreadcrumbBar breadcrumb)
{
_ownerRef = new(breadcrumb);
_spacing = spacing;
}

protected override Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize)
Expand Down Expand Up @@ -79,7 +77,6 @@ protected override Size ArrangeOverride(NonVirtualizingLayoutContext context, Si
breadcrumbItem.Arrange(new Rect(accumulatedWidths, 0, breadcrumbItem.DesiredSize.Width, breadcrumbItem.DesiredSize.Height));

accumulatedWidths += breadcrumbItem.DesiredSize.Width;
accumulatedWidths += _spacing;

VisibleItemsCount++;
}
Expand All @@ -89,15 +86,17 @@ protected override Size ArrangeOverride(NonVirtualizingLayoutContext context, Si
if (_ownerRef?.TryGetTarget(out var breadcrumbBar) ?? false)
breadcrumbBar.OnLayoutUpdated();

finalSize.Width = accumulatedWidths;

return finalSize;
}

private int GetFirstIndexToRender(NonVirtualizingLayoutContext context)
{
var itemCount = context.Children.Count;
var accumulatedWidth = _spacing;
var accumulatedWidth = 0d;

// Go through all items from the end
// Go through all items from the last item
for (int index = itemCount - 1; index >= 0; index--)
{
var newAccumulatedWidth = accumulatedWidth + context.Children[index].DesiredSize.Width;
Expand Down
11 changes: 11 additions & 0 deletions src/Files.App.Controls/Omnibar/EventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Controls
{
public record class OmnibarQuerySubmittedEventArgs(OmnibarMode Mode, object? Item, string Text);

public record class OmnibarSuggestionChosenEventArgs(OmnibarMode Mode, object SelectedItem);

public record class OmnibarTextChangedEventArgs(OmnibarMode Mode, OmnibarTextChangeReason Reason);
}
20 changes: 20 additions & 0 deletions src/Files.App.Controls/Omnibar/IOmnibarTextMemberPathProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Controls
{
/// <summary>
/// An interface that provides a way to get the text member path of <see cref="OmnibarMode.SuggestionItemsSource"/>.
/// </summary>
/// <remarks>
/// An alternative to this interface is to use an <see cref="Microsoft.UI.Xaml.Data.IBindableCustomPropertyImplementation"/> powered by CsWinRT.
/// </remarks>
public interface IOmnibarTextMemberPathProvider
{
/// <summary>
/// Retrieves the path of the text member as a string. This path can be used to identify the location of the text member.
/// </summary>
/// <returns>Returns a string representing the path of the text member.</returns>
string GetTextMemberPath(string textMemberPath);
}
}
Loading
Loading