From 2a05bc1e5c8e17696d2b135dcee33fb08e0584e8 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 17 Mar 2025 03:16:20 +0900 Subject: [PATCH] Initial commit --- .../BreadcrumbBar/BreadcrumbBar.Properties.cs | 19 ++ .../BreadcrumbBar/BreadcrumbBar.cs | 151 +++++++++ .../BreadcrumbBar/BreadcrumbBar.xaml | 303 ++++++++++++++++++ .../BreadcrumbBar/BreadcrumbBarItem.Events.cs | 46 +++ .../BreadcrumbBarItem.Properties.cs | 26 ++ .../BreadcrumbBar/BreadcrumbBarItem.cs | 91 ++++++ .../BreadcrumbBarItemAutomationPeer.cs | 54 ++++ .../BreadcrumbBarItemEventArgs.cs | 9 + .../BreadcrumbBar/BreadcrumbBarLayout.cs | 113 +++++++ src/Files.App.Controls/Themes/Generic.xaml | 6 +- tests/Files.App.UITests/Data/DummyItem2.cs | 9 + tests/Files.App.UITests/MainWindow.xaml | 3 +- tests/Files.App.UITests/MainWindow.xaml.cs | 1 + .../Views/BreadcrumbBarPage.xaml | 81 +++++ .../Views/BreadcrumbBarPage.xaml.cs | 76 +++++ 15 files changed, 986 insertions(+), 2 deletions(-) create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Events.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemEventArgs.cs create mode 100644 src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs create mode 100644 tests/Files.App.UITests/Data/DummyItem2.cs create mode 100644 tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml create mode 100644 tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml.cs diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs new file mode 100644 index 000000000000..36dca955a6c1 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs @@ -0,0 +1,19 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using CommunityToolkit.WinUI; + +namespace Files.App.Controls +{ + public partial class BreadcrumbBar : Control + { + [GeneratedDependencyProperty] + public partial FrameworkElement? RootItem { get; set; } + + [GeneratedDependencyProperty] + public partial object? ItemsSource { get; set; } + + [GeneratedDependencyProperty] + public partial object? ItemTemplate { get; set; } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs new file mode 100644 index 000000000000..b43b0ab454c1 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs @@ -0,0 +1,151 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Microsoft.UI.Xaml.Automation; +using Windows.Foundation; + +namespace Files.App.Controls +{ + public partial class BreadcrumbBar : Control + { + // Constants + + private const string TemplatePartName_RootBreadcrumbBarItem = "PART_RootBreadcrumbBarItem"; + private const string TemplatePartName_EllipsisBreadcrumbBarItem = "PART_EllipsisBreadcrumbBarItem"; + private const string TemplatePartName_MainItemsRepeater = "PART_MainItemsRepeater"; + + // Fields + + private readonly BreadcrumbBarLayout _itemsRepeaterLayout; + + private BreadcrumbBarItem? _rootBreadcrumbBarItem; + private BreadcrumbBarItem? _ellipsisBreadcrumbBarItem; + private BreadcrumbBarItem? _lastBreadcrumbBarItem; + private ItemsRepeater? _itemsRepeater; + + private bool _isEllipsisRendered; + + // Properties + + public int IndexAfterEllipsis + => _itemsRepeaterLayout.IndexAfterEllipsis; + + // Events + + public event TypedEventHandler? ItemClicked; + public event EventHandler? ItemDropDownFlyoutOpening; + public event EventHandler? ItemDropDownFlyoutClosed; + + // Constructor + + public BreadcrumbBar() + { + DefaultStyleKey = typeof(BreadcrumbBar); + + _itemsRepeaterLayout = new(this, 2d); + } + + // Methods + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _rootBreadcrumbBarItem = GetTemplateChild(TemplatePartName_RootBreadcrumbBarItem) as BreadcrumbBarItem + ?? throw new MissingFieldException($"Could not find {TemplatePartName_RootBreadcrumbBarItem} in the given {nameof(BreadcrumbBar)}'s style."); + _ellipsisBreadcrumbBarItem = GetTemplateChild(TemplatePartName_EllipsisBreadcrumbBarItem) as BreadcrumbBarItem + ?? throw new MissingFieldException($"Could not find {TemplatePartName_EllipsisBreadcrumbBarItem} in the given {nameof(BreadcrumbBar)}'s style."); + _itemsRepeater = GetTemplateChild(TemplatePartName_MainItemsRepeater) as ItemsRepeater + ?? throw new MissingFieldException($"Could not find {TemplatePartName_MainItemsRepeater} in the given {nameof(BreadcrumbBar)}'s style."); + + _rootBreadcrumbBarItem.SetOwner(this); + _ellipsisBreadcrumbBarItem.SetOwner(this); + _itemsRepeater.Layout = _itemsRepeaterLayout; + + _itemsRepeater.ElementPrepared += ItemsRepeater_ElementPrepared; + _itemsRepeater.ItemsSourceView.CollectionChanged += ItemsSourceView_CollectionChanged; + } + + internal protected virtual void RaiseItemClickedEvent(BreadcrumbBarItem item) + { + var index = _itemsRepeater?.GetElementIndex(item) ?? throw new ArgumentNullException($"{_itemsRepeater} is null."); + var eventArgs = new BreadcrumbBarItemClickedEventArgs(item, index, item == _rootBreadcrumbBarItem); + ItemClicked?.Invoke(this, eventArgs); + } + + internal protected virtual void RaiseItemDropDownFlyoutOpening(BreadcrumbBarItem item, MenuFlyout flyout) + { + var index = _itemsRepeater?.GetElementIndex(item) ?? throw new ArgumentNullException($"{_itemsRepeater} is null."); + ItemDropDownFlyoutOpening?.Invoke(this, new(flyout, item, index, item == _rootBreadcrumbBarItem)); + } + + internal protected virtual void RaiseItemDropDownFlyoutClosed(BreadcrumbBarItem item, MenuFlyout flyout) + { + var index = _itemsRepeater?.GetElementIndex(item) ?? throw new ArgumentNullException($"{_itemsRepeater} is null."); + ItemDropDownFlyoutClosed?.Invoke(this, new(flyout, item, index, item == _rootBreadcrumbBarItem)); + } + + internal protected virtual void OnLayoutUpdated() + { + if (_itemsRepeater is null) + return; + + _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; + accessibilityIndex++, collectionIndex++) + { + if (_itemsRepeater.TryGetElement(collectionIndex) is { } element) + { + element.SetValue(AutomationProperties.PositionInSetProperty, accessibilityIndex); + element.SetValue(AutomationProperties.SizeOfSetProperty, _itemsRepeaterLayout.VisibleItemsCount); + } + } + } + + internal bool TryGetElement(int index, out BreadcrumbBarItem? item) + { + item = null; + + if (_itemsRepeater is null) + return false; + + item = _itemsRepeater.TryGetElement(index) as BreadcrumbBarItem; + + return item is not null; + } + + // Event methods + + private void ItemsRepeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (args.Element is not BreadcrumbBarItem item || _itemsRepeater is null) + return; + + if (args.Index == _itemsRepeater.ItemsSourceView.Count - 1) + { + _lastBreadcrumbBarItem = item; + _lastBreadcrumbBarItem.IsLastItem = true; + } + + item.SetOwner(this); + } + + private void ItemsSourceView_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (_lastBreadcrumbBarItem is not null) + _lastBreadcrumbBarItem.IsLastItem = false; + + if (e.NewItems is not null && + e.NewItems.Count > 0 && + e.NewItems[e.NewItems.Count - 1] is BreadcrumbBarItem item) + { + _lastBreadcrumbBarItem = item; + item.IsLastItem = true; + } + } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml new file mode 100644 index 000000000000..5f92aed85ec2 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml @@ -0,0 +1,303 @@ + + + + 32 + 120 + 16 + + 4,0 + 8,0 + 16,0,8,0 + + 2,2,2,2 + 2,2,2,2 + 16,2,2,16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Events.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Events.cs new file mode 100644 index 000000000000..b0f60ecaf587 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Events.cs @@ -0,0 +1,46 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Controls +{ + public partial class BreadcrumbBarItem + { + private void ItemContentButton_Click(object sender, RoutedEventArgs e) + { + OnItemClicked(); + } + + private void ItemChevronButton_Click(object sender, RoutedEventArgs e) + { + FlyoutBase.ShowAttachedFlyout(_itemChevronButton); + } + + private void ChevronDropDownMenuFlyout_Opening(object? sender, object e) + { + if (_ownerRef is null || + _ownerRef.TryGetTarget(out var breadcrumbBar) is false || + sender is not MenuFlyout flyout) + return; + + breadcrumbBar.RaiseItemDropDownFlyoutOpening(this, flyout); + } + + private void ChevronDropDownMenuFlyout_Opened(object? sender, object e) + { + VisualStateManager.GoToState(this, "ChevronNormalOn", true); + } + + private void ChevronDropDownMenuFlyout_Closed(object? sender, object e) + { + if (_ownerRef is null || + _ownerRef.TryGetTarget(out var breadcrumbBar) is false || + sender is not MenuFlyout flyout) + return; + + breadcrumbBar.RaiseItemDropDownFlyoutClosed(this, flyout); + + VisualStateManager.GoToState(this, "ChevronNormalOff", true); + VisualStateManager.GoToState(this, "PointerNormal", true); + } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs new file mode 100644 index 000000000000..7b1709d84b26 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs @@ -0,0 +1,26 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using CommunityToolkit.WinUI; + +namespace Files.App.Controls +{ + public partial class BreadcrumbBarItem + { + [GeneratedDependencyProperty] + public partial bool IsEllipsis { get; set; } + + [GeneratedDependencyProperty] + public partial bool IsLastItem { get; set; } + + partial void OnIsEllipsisChanged(bool newValue) + { + VisualStateManager.GoToState(this, newValue ? "ChevronCollapsed" : "ChevronVisible", true); + } + + partial void OnIsLastItemChanged(bool newValue) + { + VisualStateManager.GoToState(this, newValue ? "ChevronCollapsed" : "ChevronVisible", true); + } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs new file mode 100644 index 000000000000..626a043b3e1d --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs @@ -0,0 +1,91 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Controls +{ + public partial class BreadcrumbBarItem : ContentControl + { + // Constants + + private const string TemplatePartName_ItemContentButton = "PART_ItemContentButton"; + private const string TemplatePartName_ItemChevronButton = "PART_ItemChevronButton"; + private const string TemplatePartName_ItemEllipsisDropDownMenuFlyout = "PART_ItemEllipsisDropDownMenuFlyout"; + private const string TemplatePartName_ItemChevronDropDownMenuFlyout = "PART_ItemChevronDropDownMenuFlyout"; + + // Fields + + private WeakReference? _ownerRef; + + private Button _itemContentButton = null!; + private Button _itemChevronButton = null!; + private MenuFlyout _itemEllipsisDropDownMenuFlyout = null!; + private MenuFlyout _itemChevronDropDownMenuFlyout = null!; + + // Constructor + + public BreadcrumbBarItem() + { + DefaultStyleKey = typeof(BreadcrumbBarItem); + } + + // Methods + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _itemContentButton = GetTemplateChild(TemplatePartName_ItemContentButton) as Button + ?? throw new MissingFieldException($"Could not find {TemplatePartName_ItemContentButton} in the given {nameof(BreadcrumbBarItem)}'s style."); + _itemChevronButton = GetTemplateChild(TemplatePartName_ItemChevronButton) as Button + ?? throw new MissingFieldException($"Could not find {TemplatePartName_ItemChevronButton} in the given {nameof(BreadcrumbBarItem)}'s style."); + _itemEllipsisDropDownMenuFlyout = GetTemplateChild(TemplatePartName_ItemEllipsisDropDownMenuFlyout) as MenuFlyout + ?? throw new MissingFieldException($"Could not find {TemplatePartName_ItemEllipsisDropDownMenuFlyout} in the given {nameof(BreadcrumbBarItem)}'s style."); + _itemChevronDropDownMenuFlyout = GetTemplateChild(TemplatePartName_ItemChevronDropDownMenuFlyout) as MenuFlyout + ?? throw new MissingFieldException($"Could not find {TemplatePartName_ItemChevronDropDownMenuFlyout} in the given {nameof(BreadcrumbBarItem)}'s style."); + + if (IsEllipsis || IsLastItem) + VisualStateManager.GoToState(this, "ChevronCollapsed", true); + + _itemContentButton.Click += ItemContentButton_Click; + _itemChevronButton.Click += ItemChevronButton_Click; + _itemChevronDropDownMenuFlyout.Opening += ChevronDropDownMenuFlyout_Opening; + _itemChevronDropDownMenuFlyout.Opened += ChevronDropDownMenuFlyout_Opened; + _itemChevronDropDownMenuFlyout.Closed += ChevronDropDownMenuFlyout_Closed; + } + + public void OnItemClicked() + { + if (_ownerRef is not null && + _ownerRef.TryGetTarget(out var breadcrumbBar)) + { + if (IsEllipsis) + { + // Clear items in the ellipsis flyout + _itemEllipsisDropDownMenuFlyout.Items.Clear(); + + // 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) + { + _itemEllipsisDropDownMenuFlyout.Items.Add(new MenuFlyoutItem() { Text = text }); + } + } + + // Open the ellipsis flyout + FlyoutBase.ShowAttachedFlyout(_itemContentButton); + } + else + { + // Fire a click event + breadcrumbBar.RaiseItemClickedEvent(this); + } + } + } + + public void SetOwner(BreadcrumbBar breadcrumbBar) + { + _ownerRef = new(breadcrumbBar); + } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs new file mode 100644 index 000000000000..42d81617f28c --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Automation.Provider; + +namespace Files.App.Controls +{ + public partial class BreadcrumbBarItemAutomationPeer : FrameworkElementAutomationPeer, IInvokeProvider + { + /// + /// Initializes a new instance of the BreadcrumbBarItemAutomationPeer class. + /// + /// + public BreadcrumbBarItemAutomationPeer(BreadcrumbBarItem owner) : base(owner) + { + } + + // IAutomationPeerOverrides + protected override string GetLocalizedControlTypeCore() + { + return "breadcrumb bar item"; + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface is PatternInterface.Invoke) + return this; + + return base.GetPatternCore(patternInterface); + } + + protected override string GetClassNameCore() + { + return nameof(BreadcrumbBarItem); + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Button; + } + + /// + /// Sends a request to invoke the item associated with the automation peer. + /// + public void Invoke() + { + if (Owner is not BreadcrumbBarItem item) + return; + + item.OnItemClicked(); + } + } +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemEventArgs.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemEventArgs.cs new file mode 100644 index 000000000000..c28230e0a1c0 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemEventArgs.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Controls +{ + public record class BreadcrumbBarItemClickedEventArgs(BreadcrumbBarItem Item, int Index, bool IsRootItem = false); + + public record class BreadcrumbBarItemDropDownFlyoutEventArgs(MenuFlyout Flyout, BreadcrumbBarItem? Item = null, int Index = -1, bool IsRootItem = false); +} diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs new file mode 100644 index 000000000000..09ca8502d4e0 --- /dev/null +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs @@ -0,0 +1,113 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Windows.Foundation; + +namespace Files.App.Controls +{ + /// + /// Handles layout of , collapsing items into an ellipsis button when necessary. + /// + public partial class BreadcrumbBarLayout : NonVirtualizingLayout + { + // Fields + + private readonly WeakReference? _ownerRef; + private readonly double _spacing = 0d; + + private Size _availableSize; + private BreadcrumbBarItem? _ellipsisButton = null; + + // Properties + + public bool EllipsisIsRendered { get; private set; } + public int IndexAfterEllipsis { get; private set; } + public int VisibleItemsCount { get; private set; } + + public BreadcrumbBarLayout(BreadcrumbBar breadcrumb, double spacing) + { + _ownerRef = new(breadcrumb); + _spacing = spacing; + } + + protected override Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize) + { + var accumulatedSize = new Size(0, 0); + _availableSize = availableSize; + + // Go through all items and measure them + foreach (var item in context.Children) + { + if (item is BreadcrumbBarItem breadcrumbItem) + { + breadcrumbItem.Measure(availableSize); + accumulatedSize.Width += breadcrumbItem.DesiredSize.Width; + accumulatedSize.Height = Math.Max(accumulatedSize.Height, breadcrumbItem.DesiredSize.Height); + } + } + + // Get a reference to the ellipsis item + if (context.Children.Count > 0) + _ellipsisButton ??= context.Children[0] as BreadcrumbBarItem; + + // Sets the ellipsis item's visibility based on whether the items are overflowing + EllipsisIsRendered = accumulatedSize.Width > availableSize.Width; + + return accumulatedSize; + } + + protected override Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize) + { + double accumulatedWidths = 0d; + + IndexAfterEllipsis = GetFirstIndexToRender(context); + VisibleItemsCount = 0; + + // Go through all items and arrange them + for (int index = 0; index < context.Children.Count; index++) + { + if (context.Children[index] is BreadcrumbBarItem breadcrumbItem) + { + if (index < IndexAfterEllipsis) + { + // Collapse + breadcrumbItem.Arrange(new Rect(0, 0, 0, 0)); + } + else + { + // Arrange normally + breadcrumbItem.Arrange(new Rect(accumulatedWidths, 0, breadcrumbItem.DesiredSize.Width, breadcrumbItem.DesiredSize.Height)); + + accumulatedWidths += breadcrumbItem.DesiredSize.Width; + accumulatedWidths += _spacing; + + VisibleItemsCount++; + } + } + } + + if (_ownerRef?.TryGetTarget(out var breadcrumbBar) ?? false) + breadcrumbBar.OnLayoutUpdated(); + + return finalSize; + } + + private int GetFirstIndexToRender(NonVirtualizingLayoutContext context) + { + var itemCount = context.Children.Count; + var accumulatedWidth = _spacing; + + // Go through all items from the end + for (int index = itemCount - 1; index >= 0; index--) + { + var newAccumulatedWidth = accumulatedWidth + context.Children[index].DesiredSize.Width; + if (newAccumulatedWidth >= _availableSize.Width) + return index + 1; + + accumulatedWidth = newAccumulatedWidth; + } + + return 0; + } + } +} diff --git a/src/Files.App.Controls/Themes/Generic.xaml b/src/Files.App.Controls/Themes/Generic.xaml index 895bc9763802..4e66b0e77870 100644 --- a/src/Files.App.Controls/Themes/Generic.xaml +++ b/src/Files.App.Controls/Themes/Generic.xaml @@ -55,7 +55,7 @@ - + @@ -67,6 +67,10 @@ + + + + diff --git a/tests/Files.App.UITests/Data/DummyItem2.cs b/tests/Files.App.UITests/Data/DummyItem2.cs new file mode 100644 index 000000000000..048ee1fa236e --- /dev/null +++ b/tests/Files.App.UITests/Data/DummyItem2.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System.Collections.ObjectModel; + +namespace Files.App.UITests.Data +{ + internal record DummyItem2(string Text, ObservableCollection? Children = null); +} diff --git a/tests/Files.App.UITests/MainWindow.xaml b/tests/Files.App.UITests/MainWindow.xaml index fc82d9ed9f3c..1a86ed3f22a8 100644 --- a/tests/Files.App.UITests/MainWindow.xaml +++ b/tests/Files.App.UITests/MainWindow.xaml @@ -82,7 +82,7 @@ SelectionChanged="NavigationView_SelectionChanged"> - + @@ -92,6 +92,7 @@ + diff --git a/tests/Files.App.UITests/MainWindow.xaml.cs b/tests/Files.App.UITests/MainWindow.xaml.cs index 29e688b50e13..87b3bf3800cd 100644 --- a/tests/Files.App.UITests/MainWindow.xaml.cs +++ b/tests/Files.App.UITests/MainWindow.xaml.cs @@ -50,6 +50,7 @@ private void NavigationView_SelectionChanged(NavigationView sender, NavigationVi nameof(StorageControlsPage) => typeof(StorageControlsPage), nameof(SidebarViewPage) => typeof(SidebarViewPage), nameof(OmnibarPage) => typeof(OmnibarPage), + nameof(BreadcrumbBarPage) => typeof(BreadcrumbBarPage), _ => throw new InvalidOperationException("There's no applicable page associated with the given key."), }); diff --git a/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml b/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml new file mode 100644 index 000000000000..2044818bbef3 --- /dev/null +++ b/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml.cs b/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml.cs new file mode 100644 index 000000000000..32b5cd27c0c1 --- /dev/null +++ b/tests/Files.App.UITests/Views/BreadcrumbBarPage.xaml.cs @@ -0,0 +1,76 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using CommunityToolkit.WinUI; +using Files.App.Controls; +using Files.App.UITests.Data; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System.Collections.ObjectModel; + +namespace Files.App.UITests.Views +{ + public sealed partial class BreadcrumbBarPage : Page + { + private readonly ObservableCollection DummyItems; + + [GeneratedDependencyProperty] + private partial string? ClickedItemName { get; set; } + + [GeneratedDependencyProperty] + private partial string? ClickedItemIndex { get; set; } + + [GeneratedDependencyProperty] + private partial bool IsRTLEnabled { get; set; } + + [GeneratedDependencyProperty] + private partial FlowDirection BreadcrumbBar1FlowDirection { get; set; } + + public BreadcrumbBarPage() + { + InitializeComponent(); + + DummyItems = + [ + new("Local Disk (C:)"), + new("Users"), + new("me"), + new("OneDrive"), + new("Desktop"), + new("Folder1"), + new("Folder2"), + ]; + } + + private void BreadcrumbBar1_ItemClicked(Controls.BreadcrumbBar sender, Controls.BreadcrumbBarItemClickedEventArgs args) + { + if (args.IsRootItem) + { + ClickedItemName = "Home"; + ClickedItemIndex = "Root"; + } + else + { + ClickedItemName = DummyItems[args.Index].Text; + ClickedItemIndex = $"{args.Index}"; + } + } + + private void BreadcrumbBar1_ItemDropDownFlyoutOpening(object sender, BreadcrumbBarItemDropDownFlyoutEventArgs e) + { + e.Flyout.Items.Add(new MenuFlyoutItem { Icon = new FontIcon() { Glyph = "\uE8B7" }, Text = "Item 1" }); + e.Flyout.Items.Add(new MenuFlyoutItem { Icon = new FontIcon() { Glyph = "\uE8B7" }, Text = "Item 2" }); + e.Flyout.Items.Add(new MenuFlyoutItem { Icon = new FontIcon() { Glyph = "\uE8B7" }, Text = "Item 3" }); + } + + private void BreadcrumbBar1_ItemDropDownFlyoutClosed(object sender, BreadcrumbBarItemDropDownFlyoutEventArgs e) + { + e.Flyout.Items.Clear(); + } + + partial void OnIsRTLEnabledChanged(bool newValue) + { + BreadcrumbBar1FlowDirection = IsRTLEnabled ? FlowDirection.RightToLeft : FlowDirection.LeftToRight; + } + } +}