Skip to content
Closed
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
47 changes: 8 additions & 39 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.cs
Original file line number Diff line number Diff line change
@@ -1,101 +1,82 @@
// Copyright (c) Files Community
// 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<BreadcrumbBar, BreadcrumbBarItemClickedEventArgs>? ItemClicked;
public event TypedEventHandler<BreadcrumbBar, BreadcrumbBarItemMiddleClickedEventArgs>? ItemMiddleClicked;
public event EventHandler<BreadcrumbBarItemDropDownFlyoutEventArgs>? ItemDropDownFlyoutOpening;
public event EventHandler<BreadcrumbBarItemDropDownFlyoutEventArgs>? ItemDropDownFlyoutClosed;

// Constructor

public BreadcrumbBar()
{
DefaultStyleKey = typeof(BreadcrumbBar);

_itemsRepeaterLayout = new(this);
}

// 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.ElementClearing += ItemsRepeater_ElementClearing;
_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 RaiseItemMiddleClickedEvent(BreadcrumbBarItem item)
{
var index = _itemsRepeater?.GetElementIndex(item) ?? throw new ArgumentNullException($"{_itemsRepeater} is null.");
var eventArgs = new BreadcrumbBarItemMiddleClickedEventArgs(item, index, item == _rootBreadcrumbBarItem);
ItemMiddleClicked?.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 || (_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;

for (int accessibilityIndex = 0, collectionIndex = _itemsRepeaterLayout.IndexAfterEllipsis;
accessibilityIndex < _itemsRepeaterLayout.VisibleItemsCount;
accessibilityIndex++, collectionIndex++)
Expand All @@ -107,43 +88,32 @@ internal protected virtual void OnLayoutUpdated()
}
}
}

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;

item.IsLastItem = false;
item.IsEllipsis = false;

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)
Expand All @@ -152,7 +122,6 @@ private void ItemsSourceView_CollectionChanged(object? sender, System.Collection
item.IsLastItem = true;
}
}

private void ItemsRepeater_ElementClearing(ItemsRepeater sender, ItemsRepeaterElementClearingEventArgs args)
{
if (args.Element is BreadcrumbBarItem item)
Expand Down
13 changes: 11 additions & 2 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Events.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml.Input;
using Windows.System;
using Microsoft.UI.Input;

namespace Files.App.Controls
{
Expand All @@ -13,6 +13,16 @@ private void ItemContentButton_Click(object sender, RoutedEventArgs e)
OnItemClicked();
}

private void ItemContentButton_PointerPressed(object sender, PointerRoutedEventArgs e)
{
// Check for middle mouse button click
if (e.GetCurrentPoint(sender as FrameworkElement).Properties.IsMiddleButtonPressed)
{
OnItemMiddleClicked();
e.Handled = true;
}
}

private void ItemChevronButton_Click(object sender, RoutedEventArgs e)
{
FlyoutBase.ShowAttachedFlyout(_itemChevronButton);
Expand Down Expand Up @@ -53,7 +63,6 @@ private void ChevronDropDownMenuFlyout_Closed(object? sender, object e)
return;

breadcrumbBar.RaiseItemDropDownFlyoutClosed(this, flyout);

VisualStateManager.GoToState(this, "ChevronNormalOff", true);
VisualStateManager.GoToState(this, "PointerNormal", true);
}
Expand Down
28 changes: 10 additions & 18 deletions src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
// Copyright (c) Files Community
// 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<BreadcrumbBar>? _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
Expand All @@ -42,30 +32,26 @@ protected override void OnApplyTemplate()
?? 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;
_itemContentButton.PointerPressed += ItemContentButton_PointerPressed;
_itemContentButton.PreviewKeyDown += ItemContentButton_PreviewKeyDown;
_itemChevronButton.Click += ItemChevronButton_Click;
_itemChevronButton.PreviewKeyDown += ItemChevronButton_PreviewKeyDown;
_itemChevronDropDownMenuFlyout.Opening += ChevronDropDownMenuFlyout_Opening;
_itemChevronDropDownMenuFlyout.Opened += ChevronDropDownMenuFlyout_Opened;
_itemChevronDropDownMenuFlyout.Closed += ChevronDropDownMenuFlyout_Closed;
}

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

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++)
{
Expand All @@ -76,7 +62,6 @@ public void OnItemClicked()
menuFlyoutItem.Click += (sender, e) => breadcrumbBar.RaiseItemClickedEvent(item);
}
}

// Open the ellipsis flyout
FlyoutBase.ShowAttachedFlyout(_itemContentButton);
}
Expand All @@ -86,7 +71,14 @@ public void OnItemClicked()
breadcrumbBar.RaiseItemClickedEvent(this);
}
}

public void OnItemMiddleClicked()
{
if (_ownerRef is null ||
!_ownerRef.TryGetTarget(out var breadcrumbBar))
return;
// Fire a middle click event
breadcrumbBar.RaiseItemMiddleClickedEvent(this);
}
public void SetOwner(BreadcrumbBar breadcrumbBar)
{
_ownerRef = new(breadcrumbBar);
Expand Down
5 changes: 2 additions & 3 deletions src/Files.App.Controls/BreadcrumbBar/EventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// Copyright (c) Files Community
// 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 BreadcrumbBarItemMiddleClickedEventArgs(BreadcrumbBarItem Item, int Index, bool IsRootItem = false);
public record class BreadcrumbBarItemDropDownFlyoutEventArgs(MenuFlyout Flyout, BreadcrumbBarItem? Item = null, int Index = -1, bool IsRootItem = false);
}
Loading