diff --git a/src/Files.App/Actions/Show/ToggleSidebarAction.cs b/src/Files.App/Actions/Show/ToggleSidebarAction.cs deleted file mode 100644 index aea3b9a4daea..000000000000 --- a/src/Files.App/Actions/Show/ToggleSidebarAction.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; -using Files.App.Data.Commands; -using Files.App.Extensions; -using Files.App.ViewModels; -using System.ComponentModel; -using System.Threading.Tasks; - -namespace Files.App.Actions -{ - internal class ToggleSidebarAction : ObservableObject, IToggleAction - { - private readonly SidebarViewModel viewModel; - - public string Label - => "ToggleSidebar".GetLocalizedResource(); - - public string Description - => "ToggleSidebarDescription".GetLocalizedResource(); - - public HotKey HotKey - => new(Keys.B, KeyModifiers.Ctrl); - - public bool IsOn - => viewModel.IsSidebarOpen; - - public ToggleSidebarAction() - { - viewModel = Ioc.Default.GetRequiredService(); - - viewModel.PropertyChanged += ViewModel_PropertyChanged; - } - - public Task ExecuteAsync() - { - viewModel.IsSidebarOpen = !IsOn; - - return Task.CompletedTask; - } - - private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(SidebarViewModel.IsSidebarOpen)) - OnPropertyChanged(nameof(IsOn)); - } - } -} diff --git a/src/Files.App/App.xaml b/src/Files.App/App.xaml index 07eebc154f40..cbf0529fb60f 100644 --- a/src/Files.App/App.xaml +++ b/src/Files.App/App.xaml @@ -13,9 +13,6 @@ 0 - - 32 - @@ -34,9 +31,8 @@ - + - diff --git a/src/Files.App/Data/Commands/CommandCodes.cs b/src/Files.App/Data/Commands/CommandCodes.cs index 61e1cf21d99b..d83a044f0f81 100644 --- a/src/Files.App/Data/Commands/CommandCodes.cs +++ b/src/Files.App/Data/Commands/CommandCodes.cs @@ -23,7 +23,6 @@ public enum CommandCodes ToggleShowHiddenItems, ToggleShowFileExtensions, TogglePreviewPane, - ToggleSidebar, // File System CopyItem, diff --git a/src/Files.App/Data/Commands/Manager/CommandManager.cs b/src/Files.App/Data/Commands/Manager/CommandManager.cs index c9a0cc73e873..b8467816a643 100644 --- a/src/Files.App/Data/Commands/Manager/CommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/CommandManager.cs @@ -51,7 +51,6 @@ public IRichCommand this[HotKey hotKey] public IRichCommand ToggleShowHiddenItems => commands[CommandCodes.ToggleShowHiddenItems]; public IRichCommand ToggleShowFileExtensions => commands[CommandCodes.ToggleShowFileExtensions]; public IRichCommand TogglePreviewPane => commands[CommandCodes.TogglePreviewPane]; - public IRichCommand ToggleSidebar => commands[CommandCodes.ToggleSidebar]; public IRichCommand SelectAll => commands[CommandCodes.SelectAll]; public IRichCommand InvertSelection => commands[CommandCodes.InvertSelection]; public IRichCommand ClearSelection => commands[CommandCodes.ClearSelection]; @@ -213,7 +212,6 @@ public CommandManager() [CommandCodes.ToggleShowHiddenItems] = new ToggleShowHiddenItemsAction(), [CommandCodes.ToggleShowFileExtensions] = new ToggleShowFileExtensionsAction(), [CommandCodes.TogglePreviewPane] = new TogglePreviewPaneAction(), - [CommandCodes.ToggleSidebar] = new ToggleSidebarAction(), [CommandCodes.SelectAll] = new SelectAllAction(), [CommandCodes.InvertSelection] = new InvertSelectionAction(), [CommandCodes.ClearSelection] = new ClearSelectionAction(), diff --git a/src/Files.App/Data/Commands/Manager/ICommandManager.cs b/src/Files.App/Data/Commands/Manager/ICommandManager.cs index 142952cda7a5..3b73d5c257fd 100644 --- a/src/Files.App/Data/Commands/Manager/ICommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/ICommandManager.cs @@ -25,7 +25,6 @@ public interface ICommandManager : IEnumerable IRichCommand ToggleShowHiddenItems { get; } IRichCommand ToggleShowFileExtensions { get; } IRichCommand TogglePreviewPane { get; } - IRichCommand ToggleSidebar { get; } IRichCommand CopyItem { get; } IRichCommand CopyPath { get; } diff --git a/src/Files.App/Data/Contexts/Tags/TagsContext.cs b/src/Files.App/Data/Contexts/Tags/TagsContext.cs index f43e6329de5a..6564e22a4e16 100644 --- a/src/Files.App/Data/Contexts/Tags/TagsContext.cs +++ b/src/Files.App/Data/Contexts/Tags/TagsContext.cs @@ -30,7 +30,7 @@ sealed class TagsContext : ITagsContext public TagsContext() { FileTagsContainerViewModel.SelectedTagChanged += SelectedTagsChanged; - SidebarControl.SelectedTagChanged += SelectedTagsChanged; + SidebarViewModel.SelectedTagChanged += SelectedTagsChanged; } private void SelectedTagsChanged(object _, SelectedTagChangedEventArgs e) diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index f076c379c6d7..78b533a86d53 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -6,7 +6,9 @@ using Files.Core.Storage.Enums; using Files.Core.Storage.LocatableStorage; using Files.Core.Storage.NestedStorage; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; using Windows.Storage; using Windows.Storage.Streams; @@ -20,7 +22,11 @@ public class DriveItem : ObservableObject, INavigationControlItem, ILocatableFol public BitmapImage Icon { get => icon; - set => SetProperty(ref icon, value); + set + { + SetProperty(ref icon, value, nameof(Icon)); + OnPropertyChanged(nameof(IconSource)); + } } public byte[] IconData { get; set; } @@ -32,8 +38,6 @@ public string Path set => path = value; } - public string ToolTipText { get; private set; } - public string DeviceID { get; set; } public StorageFolder Root { get; set; } @@ -68,7 +72,10 @@ public ByteSize MaxSpace { if (SetProperty(ref maxSpace, value)) { - ToolTipText = GetSizeString(); + if (Type != DriveType.CloudDrive) + { + ToolTip = GetSizeString(); + } OnPropertyChanged(nameof(MaxSpaceText)); OnPropertyChanged(nameof(ShowDriveDetails)); @@ -84,7 +91,10 @@ public ByteSize FreeSpace { if (SetProperty(ref freeSpace, value)) { - ToolTipText = GetSizeString(); + if (Type != DriveType.CloudDrive) + { + ToolTip = GetSizeString(); + } OnPropertyChanged(nameof(FreeSpaceText)); } @@ -107,7 +117,22 @@ public ByteSize SpaceUsed public bool ShowDriveDetails => MaxSpace.Bytes > 0d; - public DriveType Type { get; set; } + private DriveType type; + public DriveType Type + { + get => type; set + { + type = value; + if (value == DriveType.Network) + { + ToolTip = "Network".GetLocalizedResource(); + } + else if (value == DriveType.CloudDrive) + { + ToolTip = Text; + } + } + } private string text; public string Text @@ -152,6 +177,28 @@ public bool ShowStorageSense public string Name => Root.DisplayName; + public object? Children => null; + + private object toolTip = ""; + public object ToolTip + { + get => toolTip; + set + { + SetProperty(ref toolTip, value); + } + } + + public bool IsExpanded { get => false; set { } } + + public IconSource? IconSource + { + get => new ImageIconSource() + { + ImageSource = Icon + }; + } + public static async Task CreateFromPropertiesAsync(StorageFolder root, string deviceId, string label, DriveType type, IRandomAccessStream imageStream = null) { var item = new DriveItem(); @@ -255,7 +302,6 @@ public async Task LoadThumbnailAsync(bool isSidebar = false) } IconData ??= UIHelpers.GetSidebarIconResourceInfo(Constants.ImageRes.Folder).IconData; } - Icon ??= await IconData.ToBitmapAsync(); } diff --git a/src/Files.App/Data/Items/FileTagItem.cs b/src/Files.App/Data/Items/FileTagItem.cs index 7b18b3f76c81..3a74b7dbbfed 100644 --- a/src/Files.App/Data/Items/FileTagItem.cs +++ b/src/Files.App/Data/Items/FileTagItem.cs @@ -1,11 +1,17 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using CommunityToolkit.WinUI.Helpers; +using Files.App.Converters; using Files.Core.ViewModels.FileTags; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media; namespace Files.App.Data.Items { - public class FileTagItem : INavigationControlItem + public class FileTagItem : ObservableObject, INavigationControlItem { public string Text { get; set; } @@ -16,7 +22,8 @@ public string Path set { path = value; - ToolTipText = Text; + OnPropertyChanged(nameof(IconSource)); + OnPropertyChanged(nameof(ToolTip)); } } @@ -33,5 +40,20 @@ public int CompareTo(INavigationControlItem other) => Text.CompareTo(other.Text); public TagViewModel FileTag { get; set; } + + public object? Children => null; + + public IconSource? IconSource + { + get => new PathIconSource() + { + Data = (Geometry)XamlBindingHelper.ConvertValue(typeof(Geometry), (string)Application.Current.Resources["ColorIconFilledTag"]), + Foreground = new SolidColorBrush(FileTag.Color.ToColor()) + }; + } + + public object ToolTip => Text; + + public bool IsExpanded { get => false; set { } } } } diff --git a/src/Files.App/Data/Items/INavigationControlItem.cs b/src/Files.App/Data/Items/INavigationControlItem.cs index 56da8d24c6ab..6d3802161eb1 100644 --- a/src/Files.App/Data/Items/INavigationControlItem.cs +++ b/src/Files.App/Data/Items/INavigationControlItem.cs @@ -1,18 +1,20 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.App.UserControls.Sidebar; +using Microsoft.UI.Xaml.Controls; + namespace Files.App.Data.Items { - public interface INavigationControlItem : IComparable + + public interface INavigationControlItem : IComparable, INotifyPropertyChanged, ISidebarItemModel { - public string Text { get; } + public new string Text { get; } public string Path { get; } public SectionType Section { get; } - public string ToolTipText { get; } - public NavigationControlItemType ItemType { get; } public ContextMenuOptions MenuOptions { get; } diff --git a/src/Files.App/Data/Items/LocationItem.cs b/src/Files.App/Data/Items/LocationItem.cs index e8bb1cce6524..78cf1c37041b 100644 --- a/src/Files.App/Data/Items/LocationItem.cs +++ b/src/Files.App/Data/Items/LocationItem.cs @@ -1,8 +1,7 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.WinUI; -using Files.App.Utils.Shell; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; using System.IO; @@ -14,14 +13,27 @@ public class LocationItem : ObservableObject, INavigationControlItem public BitmapImage Icon { get => icon; - set => SetProperty(ref icon, value); + set + { + SetProperty(ref icon, value, nameof(Icon)); + OnPropertyChanged(nameof(IconSource)); + } } - //public Uri IconSource { get; set; } - public byte[] IconData { get; set; } - public string Text { get; set; } = ""; + private string text = ""; + public string Text + { + get => text; + set + { + text = value; + // Just in case path hasn't been set + if (ToolTip is "") + ToolTip = value; + } + } private string path; public string Path @@ -30,7 +42,7 @@ public string Path set { path = value; - ToolTipText = string.IsNullOrEmpty(Path) || + ToolTip = string.IsNullOrEmpty(Path) || Path.Contains('?', StringComparison.Ordinal) || Path.StartsWith("shell:", StringComparison.OrdinalIgnoreCase) || Path.EndsWith(ShellLibraryItem.EXTENSION, StringComparison.OrdinalIgnoreCase) || @@ -40,14 +52,20 @@ public string Path } } - public virtual string ToolTipText { get; set; } - public NavigationControlItemType ItemType => NavigationControlItemType.Location; public bool IsDefaultLocation { get; set; } - public BulkConcurrentObservableCollection ChildItems { get; set; } + public object? Children => Section == SectionType.Home ? null : ChildItems; + public BulkConcurrentObservableCollection? ChildItems { get; set; } + public IconSource? IconSource + { + get => new ImageIconSource() + { + ImageSource = icon + }; + } public bool SelectsOnInvoked { get; set; } = true; @@ -68,6 +86,16 @@ public bool IsExpanded public bool IsHeader { get; set; } + private object toolTip = ""; + public object ToolTip + { + get => toolTip; + set + { + SetProperty(ref toolTip, value); + } + } + public int CompareTo(INavigationControlItem other) => Text.CompareTo(other.Text); @@ -91,14 +119,9 @@ public ulong SpaceUsed set { SetProperty(ref spaceUsed, value); - - MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => OnPropertyChanged(nameof(ToolTipText))); } } - public override string ToolTipText - => SpaceUsed.ToSizeString(); - public RecycleBinLocationItem() { SpaceUsed = RecycleBinHelpers.GetSize(); diff --git a/src/Files.App/Data/Items/WslDistroItem.cs b/src/Files.App/Data/Items/WslDistroItem.cs index 2be4fd90d89e..6f373323d9df 100644 --- a/src/Files.App/Data/Items/WslDistroItem.cs +++ b/src/Files.App/Data/Items/WslDistroItem.cs @@ -1,9 +1,11 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Microsoft.UI.Xaml.Controls; + namespace Files.App.Data.Items { - public class WslDistroItem : INavigationControlItem + public class WslDistroItem : ObservableObject, INavigationControlItem { public string Text { get; set; } @@ -14,21 +16,51 @@ public string Path set { path = value; - ToolTipText = Path.Contains('?', StringComparison.Ordinal) ? Text : Path; + ToolTip = Path.Contains('?', StringComparison.Ordinal) ? Text : Path; } } - public string ToolTipText { get; private set; } - public NavigationControlItemType ItemType => NavigationControlItemType.LinuxDistro; - public Uri Logo { get; set; } + private Uri icon; + public Uri Icon + { + get => icon; + set + { + SetProperty(ref icon, value, nameof(Icon)); + OnPropertyChanged(nameof(IconSource)); + } + } public SectionType Section { get; set; } public ContextMenuOptions MenuOptions { get; set; } + public object? Children => null; + + private object toolTip = ""; + public object ToolTip + { + get => toolTip; + set + { + SetProperty(ref toolTip, value); + } + } + + public bool IsExpanded { get => false; set { } } + + public IconSource? IconSource + { + get => new BitmapIconSource() + { + UriSource = icon, + ShowAsMonochrome = false, + }; + } + public int CompareTo(INavigationControlItem other) => Text.CompareTo(other.Text); } } diff --git a/src/Files.App/Data/Models/SidebarPinnedModel.cs b/src/Files.App/Data/Models/SidebarPinnedModel.cs index 15e8f26c2a55..7b0956fa5863 100644 --- a/src/Files.App/Data/Models/SidebarPinnedModel.cs +++ b/src/Files.App/Data/Models/SidebarPinnedModel.cs @@ -100,6 +100,11 @@ public async Task CreateLocationItemFromPathAsync(string path) locationItem.IsDefaultLocation = false; locationItem.Text = res.Result?.DisplayName ?? Path.GetFileName(path.TrimEnd('\\')); + if(locationItem is RecycleBinLocationItem recycleBinItem) + { + recycleBinItem.ToolTip = recycleBinItem.SpaceUsed.ToSizeString(); + } + if (res || (FilesystemResult)FolderHelpers.CheckFolderAccessWithWin32(path)) { locationItem.IsInvalid = false; diff --git a/src/Files.App/Dialogs/ReorderSidebarItemsDialog.xaml b/src/Files.App/Dialogs/ReorderSidebarItemsDialog.xaml index 084ad270b8cc..61a6129be386 100644 --- a/src/Files.App/Dialogs/ReorderSidebarItemsDialog.xaml +++ b/src/Files.App/Dialogs/ReorderSidebarItemsDialog.xaml @@ -30,7 +30,7 @@ DragStarting="ListViewItem_DragStarting" Drop="ListViewItem_Drop" Tag="{x:Bind Path}" - ToolTipService.ToolTip="{x:Bind ToolTipText, Mode=OneWay}"> + ToolTipService.ToolTip="{x:Bind ToolTip, Mode=OneWay}"> diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index f4d6fd9460da..75b752a72284 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -68,6 +68,11 @@ + + + + + @@ -130,4 +135,13 @@ + + + $(DefaultXamlRuntime) + + + MSBuild:Compile + + + diff --git a/src/Files.App/ResourceDictionaries/SideBarResources.xaml b/src/Files.App/ResourceDictionaries/SideBarResources.xaml new file mode 100644 index 000000000000..a0b6502bb38f --- /dev/null +++ b/src/Files.App/ResourceDictionaries/SideBarResources.xaml @@ -0,0 +1,404 @@ + + + + + + 300 + -300 + 56 + -56 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App/Services/QuickAccessService.cs b/src/Files.App/Services/QuickAccessService.cs index 71361da1ebf7..91d3c32a52bb 100644 --- a/src/Files.App/Services/QuickAccessService.cs +++ b/src/Files.App/Services/QuickAccessService.cs @@ -12,8 +12,9 @@ internal class QuickAccessService : IQuickAccessService public async Task> GetPinnedFoldersAsync() { - return (await Win32Shell.GetShellFolderAsync(guid, "Enumerate", 0, int.MaxValue, "System.Home.IsPinned")).Enumerate + var result = (await Win32Shell.GetShellFolderAsync(guid, "Enumerate", 0, int.MaxValue, "System.Home.IsPinned")).Enumerate .Where(link => link.IsFolder); + return result; } public Task PinToSidebar(string folderPath) diff --git a/src/Files.App/Services/ResourcesService.cs b/src/Files.App/Services/ResourcesService.cs index 162b234a066c..83eedca8e3c8 100644 --- a/src/Files.App/Services/ResourcesService.cs +++ b/src/Files.App/Services/ResourcesService.cs @@ -47,11 +47,9 @@ public void SetCompactSpacing(bool useCompactSpacing) { var listItemHeight = useCompactSpacing ? 28 : 36; var listItemMargin = useCompactSpacing ? "-2" : "0"; - var navigationViewItemOnLeftMinHeight = useCompactSpacing ? 20 : 32; Application.Current.Resources["ListItemHeight"] = listItemHeight; Application.Current.Resources["ListItemMargin"] = listItemMargin; - Application.Current.Resources["NavigationViewItemOnLeftMinHeight"] = navigationViewItemOnLeftMinHeight; } /// diff --git a/src/Files.App/UserControls/InnerNavigationToolbar.xaml b/src/Files.App/UserControls/InnerNavigationToolbar.xaml index fd617741e81d..1d0d268c3a9d 100644 --- a/src/Files.App/UserControls/InnerNavigationToolbar.xaml +++ b/src/Files.App/UserControls/InnerNavigationToolbar.xaml @@ -41,7 +41,7 @@ @@ -795,7 +795,7 @@ Height="120" HorizontalAlignment="Center" VerticalAlignment="Center" - BorderBrush="{ThemeResource ControlStrokeColorDefault}" + BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderThickness="1" /> @@ -836,7 +836,7 @@ diff --git a/src/Files.App/UserControls/Pane/PreviewPane.xaml b/src/Files.App/UserControls/Pane/PreviewPane.xaml index 182b448c3909..0b0c2eba7b85 100644 --- a/src/Files.App/UserControls/Pane/PreviewPane.xaml +++ b/src/Files.App/UserControls/Pane/PreviewPane.xaml @@ -138,15 +138,15 @@ - + - + @@ -160,7 +160,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Background="{ThemeResource SubtleFillColorTertiaryBrush}" - BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}" + BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="4" Orientation="Horizontal"> diff --git a/src/Files.App/UserControls/SideBar/ISideBarViewModel.cs b/src/Files.App/UserControls/SideBar/ISideBarViewModel.cs new file mode 100644 index 000000000000..45f56570ea0a --- /dev/null +++ b/src/Files.App/UserControls/SideBar/ISideBarViewModel.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; +using Windows.ApplicationModel.DataTransfer; +using Windows.Foundation; + +namespace Files.App.UserControls.Sidebar +{ + public record ItemDroppedEventArgs(object DropTarget, DataPackageView DroppedItem, SidebarItemDropPosition dropPosition, DragEventArgs RawEvent) { } + public record ItemDragOverEventArgs(object DropTarget, DataPackageView DroppedItem, SidebarItemDropPosition dropPosition, DragEventArgs RawEvent) { } + public record ItemContextInvokedArgs(object? Item, Point Position) { } + + public interface ISidebarViewModel + { + /// + /// The source/list of items that will be rendered in the sidebar + /// + object SidebarItems { get; } + + /// + /// Gets invoked when the context was requested for an item in the sidebar. + /// Also applies when context was requested for the pane itsself. + /// + /// The sender of this event + /// The for this event. + void HandleItemContextInvoked(object sender, ItemContextInvokedArgs args); + + /// + /// Gets invoked when an item drags over any item of the sidebar. + /// + /// The for this event. + void HandleItemDragOver(ItemDragOverEventArgs args); + + /// + /// Gets invoked when an item is dropped on any item of the sidebar. + /// + /// The for this event. + void HandleItemDropped(ItemDroppedEventArgs args); + + /// + /// Gets invoked when an item is invoked (double clicked) on any item of the sidebar. + /// + /// The item that was invoked. + void HandleItemInvoked(object item); + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarDisplayMode.cs b/src/Files.App/UserControls/SideBar/SideBarDisplayMode.cs new file mode 100644 index 000000000000..3ce0c62b1b71 --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarDisplayMode.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.UserControls.Sidebar +{ + /// + /// The display mode of the + /// + public enum SidebarDisplayMode + { + /// + /// The sidebar is hidden and moves in from the side when the is set to true. + /// + Minimal, + /// + /// Only the icons of the top most sections are visible. + /// + Compact, + /// + /// The sidebar is expanded and items can also be expanded. + /// + Expanded + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarItem.cs b/src/Files.App/UserControls/SideBar/SideBarItem.cs new file mode 100644 index 000000000000..06355b4c4f61 --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarItem.cs @@ -0,0 +1,448 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using CommunityToolkit.WinUI.UI; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Windows.ApplicationModel.DataTransfer; + +namespace Files.App.UserControls.Sidebar +{ + public sealed partial class SidebarItem : Control + { + private const double DROP_REPOSITION_THRESHOLD = 0.2; // Percentage of top/bottom at which we consider a drop to be a reposition/insertion + + public bool HasChildren => Item?.Children is IList enumerable && enumerable.Count > 0; + public bool IsGroupHeader => Item?.Children is not null; + public bool CollapseEnabled => DisplayMode != SidebarDisplayMode.Compact; + + private bool hasChildSelection => selectedChildItem != null; + private bool isPointerOver = false; + private object? selectedChildItem = null; + private ItemsRepeater? childrenRepeater; + private ISidebarItemModel? lastSubscriber; + + public SidebarItem() + { + DefaultStyleKey = typeof(SidebarItem); + + PointerReleased += Item_PointerReleased; + KeyDown += (sender, args) => + { + if (args.Key == Windows.System.VirtualKey.Enter) + { + Clicked(); + args.Handled = true; + } + }; + DragStarting += SidebarItem_DragStarting; + + Loaded += SidebarItem_Loaded; + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SidebarItemAutomationPeer(this); + } + + internal void Select() + { + if (Owner is not null) + Owner.SelectedItem = Item!; + } + + private void SidebarItem_Loaded(object sender, RoutedEventArgs e) + { + HookupOwners(); + + if (GetTemplateChild("ElementGrid") is Grid grid) + { + grid.PointerEntered += ItemGrid_PointerEntered; + grid.PointerExited += ItemGrid_PointerExited; + grid.PointerCanceled += ItemGrid_PointerCanceled; + grid.PointerPressed += ItemGrid_PointerPressed; + grid.ContextRequested += ItemGrid_ContextRequested; + grid.DragLeave += ItemGrid_DragLeave; + grid.DragOver += ItemGrid_DragOver; + grid.Drop += ItemGrid_Drop; + grid.AllowDrop = true; + grid.IsTabStop = true; + } + + if (GetTemplateChild("ChildrenPresenter") is ItemsRepeater repeater) + { + childrenRepeater = repeater; + repeater.ElementPrepared += ChildrenPresenter_ElementPrepared; + repeater.SizeChanged += ChildrenPresenter_SizeChanged; + } + if (GetTemplateChild("FlyoutChildrenPresenter") is ItemsRepeater flyoutRepeater) + { + flyoutRepeater.ElementPrepared += ChildrenPresenter_ElementPrepared; + } + + HandleItemChange(); + } + + public void HandleItemChange() + { + HookupItemChangeListener(null, Item); + UpdateExpansionState(); + ReevaluateSelection(); + } + + private void ChildrenPresenter_SizeChanged(object sender, SizeChangedEventArgs e) + { + if(e.NewSize.Height > 1) + { + ChildrenPresenterHeight = e.NewSize.Height; + } + } + + private void HookupOwners() + { + FrameworkElement resolvingTarget = this; + if (GetTemplateRoot(Parent) is FrameworkElement element) + { + resolvingTarget = element; + } + Owner = resolvingTarget.FindAscendant()!; + + Owner.RegisterPropertyChangedCallback(SidebarView.DisplayModeProperty, (sender, args) => + { + DisplayMode = Owner.DisplayMode; + }); + DisplayMode = Owner.DisplayMode; + + Owner.RegisterPropertyChangedCallback(SidebarView.SelectedItemProperty, (sender, args) => + { + ReevaluateSelection(); + }); + ReevaluateSelection(); + } + + private void HookupItemChangeListener(ISidebarItemModel? oldItem, ISidebarItemModel? newItem) + { + if (lastSubscriber != null) + { + lastSubscriber.PropertyChanged -= ItemPropertyChangedHandler; + if (lastSubscriber.Children is INotifyCollectionChanged observableCollection) + observableCollection.CollectionChanged -= ChildItems_CollectionChanged; + } + + if (oldItem != null) + { + oldItem.PropertyChanged -= ItemPropertyChangedHandler; + if (oldItem.Children is INotifyCollectionChanged observableCollection) + observableCollection.CollectionChanged -= ChildItems_CollectionChanged; + } + if (newItem != null) + { + newItem.PropertyChanged += ItemPropertyChangedHandler; + lastSubscriber = newItem; + if (newItem.Children is INotifyCollectionChanged observableCollection) + observableCollection.CollectionChanged += ChildItems_CollectionChanged; + } + UpdateIcon(); + } + + private void SidebarItem_DragStarting(UIElement sender, DragStartingEventArgs args) + { + args.Data.SetData(StandardDataFormats.Text, Item!.Text.ToString()); + } + + private void SetFlyoutOpen(bool isOpen = true) + { + if (Item?.Children is null) return; + + var flyoutOwner = (GetTemplateChild("ElementGrid") as FrameworkElement)!; + if (isOpen) + { + FlyoutBase.ShowAttachedFlyout(flyoutOwner); + } + else + { + FlyoutBase.GetAttachedFlyout(flyoutOwner).Hide(); + } + } + + private void ChildItems_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + ReevaluateSelection(); + UpdateExpansionState(); + if(DisplayMode == SidebarDisplayMode.Compact && !HasChildren) + { + SetFlyoutOpen(false); + } + } + + void ItemPropertyChangedHandler(object? sender, PropertyChangedEventArgs args) + { + if (args.PropertyName == nameof(ISidebarItemModel.IconSource)) + { + UpdateIcon(); + } + } + + private void ReevaluateSelection() + { + if (!IsGroupHeader) + { + IsSelected = Item == Owner?.SelectedItem; + if (IsSelected) + { + Owner?.UpdateSelectedItemContainer(this); + } + } + else if (Item?.Children is IList list) + { + if (list.Contains(Owner?.SelectedItem)) + { + selectedChildItem = Owner?.SelectedItem; + SetFlyoutOpen(false); + } + else + { + selectedChildItem = null; + } + UpdateSelectionState(); + } + } + + private void ChildrenPresenter_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (args.Element is SidebarItem item) + { + if (Item?.Children is IList enumerable) + { + var newElement = enumerable[args.Index]; + if (newElement == selectedChildItem) + { + (args.Element as SidebarItem)!.IsSelected = true; + } + else + { + (args.Element as SidebarItem)!.IsSelected = false; + } + item.HandleItemChange(); + } + } + } + + internal void Clicked() + { + if (IsGroupHeader) + { + if (CollapseEnabled) + { + IsExpanded = !IsExpanded; + } + else if(HasChildren) + { + SetFlyoutOpen(true); + } + } + RaiseItemInvoked(); + } + + internal void RaiseItemInvoked() + { + Owner?.RaiseItemInvoked(this); + } + + private void SidebarDisplayModeChanged(SidebarDisplayMode oldValue) + { + var useAnimations = oldValue != SidebarDisplayMode.Minimal; + switch (DisplayMode) + { + case SidebarDisplayMode.Expanded: + UpdateExpansionState(useAnimations); + UpdateSelectionState(); + SetFlyoutOpen(false); + break; + case SidebarDisplayMode.Minimal: + UpdateExpansionState(useAnimations); + SetFlyoutOpen(false); + break; + case SidebarDisplayMode.Compact: + UpdateExpansionState(useAnimations); + UpdateSelectionState(); + break; + } + if (!IsInFlyout) + { + VisualStateManager.GoToState(this, DisplayMode == SidebarDisplayMode.Compact ? "Compact" : "NonCompact", true); + } + } + + private void UpdateSelectionState() + { + VisualStateManager.GoToState(this, ShouldShowSelectionIndicator() ? "Selected" : "Unselected", true); + UpdatePointerState(); + } + + private void UpdateIcon() + { + Icon = Item?.IconSource?.CreateIconElement(); + if (Icon is not null) + AutomationProperties.SetAccessibilityView(Icon, AccessibilityView.Raw); + } + + private bool ShouldShowSelectionIndicator() + { + if (IsExpanded && CollapseEnabled) + { + return IsSelected; + } + else + { + return IsSelected || hasChildSelection; + } + } + + private void UpdatePointerState(bool isPointerDown = false) + { + var useSelectedState = ShouldShowSelectionIndicator(); + if (isPointerDown) + { + VisualStateManager.GoToState(this, useSelectedState ? "PressedSelected" : "Pressed", true); + } + else if (isPointerOver) + { + VisualStateManager.GoToState(this, useSelectedState ? "PointerOverSelected" : "PointerOver", true); + } + else + { + VisualStateManager.GoToState(this, useSelectedState ? "NormalSelected" : "Normal", true); + } + } + + private void UpdateExpansionState(bool useAnimations = true) + { + if(Item?.Children is null || !CollapseEnabled) + { + VisualStateManager.GoToState(this, "NoExpansion", useAnimations); + } + else if (!HasChildren) + { + VisualStateManager.GoToState(this, "NoChildren", useAnimations); + } + else + { + if (childrenRepeater != null) + { + if (childrenRepeater.ActualHeight > ChildrenPresenterHeight) + { + ChildrenPresenterHeight = childrenRepeater.ActualHeight; + } + } + VisualStateManager.GoToState(this, IsExpanded ? "Expanded" : "Collapsed", useAnimations); + VisualStateManager.GoToState(this, IsExpanded ? "ExpandedIconNormal" : "CollapsedIconNormal", useAnimations); + } + UpdateSelectionState(); + } + + private void ItemGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) + { + isPointerOver = true; + UpdatePointerState(); + } + + private void ItemGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) + { + isPointerOver = false; + UpdatePointerState(); + } + + private void ItemGrid_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) + { + UpdatePointerState(); + } + + private void ItemGrid_PointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) + { + UpdatePointerState(true); + VisualStateManager.GoToState(this, IsExpanded ? "ExpandedIconPressed" : "CollapsedIconPressed", true); + } + + private void Item_PointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) + { + e.Handled = true; + UpdatePointerState(); + + VisualStateManager.GoToState(this, IsExpanded ? "ExpandedIconNormal" : "CollapsedIconNormal", true); + var updateKind = e.GetCurrentPoint(null).Properties.PointerUpdateKind; + if (updateKind == PointerUpdateKind.LeftButtonReleased) + { + Clicked(); + } + } + + private void ItemGrid_DragOver(object sender, DragEventArgs e) + { + if (HasChildren) + { + IsExpanded = true; + } + + var insertsAbove = DetermineDropTargetPosition(e); + if (insertsAbove == SidebarItemDropPosition.Center) + { + VisualStateManager.GoToState(this, "DragOnTop", true); + } + else if (insertsAbove == SidebarItemDropPosition.Top) + { + VisualStateManager.GoToState(this, "DragInsertAbove", true); + } + else if (insertsAbove == SidebarItemDropPosition.Bottom) + { + VisualStateManager.GoToState(this, "DragInsertBelow", true); + } + + Owner?.RaiseItemDragOver(this, insertsAbove, e); + } + + private void ItemGrid_ContextRequested(UIElement sender, Microsoft.UI.Xaml.Input.ContextRequestedEventArgs args) + { + Owner?.RaiseContextRequested(this, args.TryGetPosition(this, out var point) ? point : default); + args.Handled = true; + } + + private void ItemGrid_DragLeave(object sender, DragEventArgs e) + { + UpdatePointerState(); + } + + private void ItemGrid_Drop(object sender, DragEventArgs e) + { + UpdatePointerState(); + Owner?.RaiseItemDropped(this, DetermineDropTargetPosition(e), e); + } + + private SidebarItemDropPosition DetermineDropTargetPosition(DragEventArgs args) + { + if (UseReorderDrop) + { + if (GetTemplateChild("ElementGrid") is Grid grid) + { + var position = args.GetPosition(grid); + if (position.Y < grid.ActualHeight * DROP_REPOSITION_THRESHOLD) + { + return SidebarItemDropPosition.Top; + } + if (position.Y > grid.ActualHeight * (1 - DROP_REPOSITION_THRESHOLD)) + { + return SidebarItemDropPosition.Bottom; + } + return SidebarItemDropPosition.Center; + } + } + return SidebarItemDropPosition.Center; + } + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarItem.properties.cs b/src/Files.App/UserControls/SideBar/SideBarItem.properties.cs new file mode 100644 index 000000000000..58cabe7a0d18 --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarItem.properties.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Files.App.UserControls.Sidebar +{ + public sealed partial class SidebarItem : Control + { + public SidebarView? Owner + { + get { return (SidebarView?)GetValue(OwnerProperty); } + set { SetValue(OwnerProperty, value); } + } + public static readonly DependencyProperty OwnerProperty = + DependencyProperty.Register(nameof(Owner), typeof(SidebarView), typeof(SidebarItem), new PropertyMetadata(null)); + + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + public static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register(nameof(IsSelected), typeof(bool), typeof(SidebarItem), new PropertyMetadata(false, OnPropertyChanged)); + + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(SidebarItem), new PropertyMetadata(true, OnPropertyChanged)); + + public bool IsInFlyout + { + get { return (bool)GetValue(IsInFlyoutProperty); } + set { SetValue(IsInFlyoutProperty, value); } + } + public static readonly DependencyProperty IsInFlyoutProperty = + DependencyProperty.Register(nameof(IsInFlyout), typeof(bool), typeof(SidebarItem), new PropertyMetadata(false)); + + public double ChildrenPresenterHeight + { + get { return (double)GetValue(ChildrenPresenterHeightProperty); } + set { SetValue(ChildrenPresenterHeightProperty, value); } + } + // Using 30 as a default in case something goes wrong + public static readonly DependencyProperty ChildrenPresenterHeightProperty = + DependencyProperty.Register(nameof(ChildrenPresenterHeight), typeof(double), typeof(SidebarItem), new PropertyMetadata(30d)); + + public ISidebarItemModel? Item + { + get { return (ISidebarItemModel)GetValue(ItemProperty); } + set { SetValue(ItemProperty, value); } + } + public static readonly DependencyProperty ItemProperty = + DependencyProperty.Register(nameof(Item), typeof(ISidebarItemModel), typeof(SidebarItem), new PropertyMetadata(null)); + + public bool UseReorderDrop + { + get { return (bool)GetValue(UseReorderDropProperty); } + set { SetValue(UseReorderDropProperty, value); } + } + public static readonly DependencyProperty UseReorderDropProperty = + DependencyProperty.Register(nameof(UseReorderDrop), typeof(bool), typeof(SidebarItem), new PropertyMetadata(false)); + + public FrameworkElement? Icon + { + get { return (FrameworkElement?)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + public static readonly DependencyProperty IconProperty = + DependencyProperty.Register(nameof(Icon), typeof(FrameworkElement), typeof(SidebarItem), new PropertyMetadata(null)); + + public SidebarDisplayMode DisplayMode + { + get { return (SidebarDisplayMode)GetValue(DisplayModeProperty); } + set { SetValue(DisplayModeProperty, value); } + } + public static readonly DependencyProperty DisplayModeProperty = + DependencyProperty.Register(nameof(DisplayMode), typeof(SidebarDisplayMode), typeof(SidebarItem), new PropertyMetadata(SidebarDisplayMode.Expanded, OnPropertyChanged)); + + public static void SetTemplateRoot(DependencyObject target, FrameworkElement value) + { + target.SetValue(TemplateRootProperty, value); + } + public static FrameworkElement GetTemplateRoot(DependencyObject target) + { + return (FrameworkElement)target.GetValue(TemplateRootProperty); + } + public static readonly DependencyProperty TemplateRootProperty = + DependencyProperty.Register("TemplateRoot", typeof(FrameworkElement), typeof(FrameworkElement), new PropertyMetadata(null)); + + public static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (sender is not SidebarItem item) return; + if (e.Property == DisplayModeProperty) + { + item.SidebarDisplayModeChanged((SidebarDisplayMode)e.OldValue); + } + else if (e.Property == IsSelectedProperty) + { + item.UpdateSelectionState(); + } + else if (e.Property == IsExpandedProperty) + { + item.UpdateExpansionState(); + } + else if(e.Property == ItemProperty) + { + item.HandleItemChange(); + } + else + { + Debug.Write(e.Property.ToString()); + } + } + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarResources.cs b/src/Files.App/UserControls/SideBar/SideBarResources.cs new file mode 100644 index 000000000000..95851b0168d6 --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarResources.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; + +namespace Files.App.UserControls.Sidebar +{ + /// + /// Backing dictionary for Sidebar Resources to support x:Bind + /// + partial class SidebarResources : ResourceDictionary + { + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarView.properties.cs b/src/Files.App/UserControls/SideBar/SideBarView.properties.cs new file mode 100644 index 000000000000..9693af74871f --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarView.properties.cs @@ -0,0 +1,99 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; + +namespace Files.App.UserControls.Sidebar +{ + public sealed partial class SidebarView + { + public SidebarDisplayMode DisplayMode + { + get { return (SidebarDisplayMode)GetValue(DisplayModeProperty); } + set { SetValue(DisplayModeProperty, value); } + } + public static readonly DependencyProperty DisplayModeProperty = + DependencyProperty.Register(nameof(DisplayMode), typeof(SidebarDisplayMode), typeof(SidebarView), new PropertyMetadata(SidebarDisplayMode.Expanded, OnPropertyChanged)); + + public UIElement InnerContent + { + get { return (UIElement)GetValue(InnerContentProperty); } + set { SetValue(InnerContentProperty, value); } + } + public static readonly DependencyProperty InnerContentProperty = + DependencyProperty.Register(nameof(InnerContent), typeof(UIElement), typeof(SidebarView), new PropertyMetadata(null)); + + public UIElement Footer + { + get { return (UIElement)GetValue(FooterProperty); } + set { SetValue(FooterProperty, value); } + } + public static readonly DependencyProperty FooterProperty = + DependencyProperty.Register("Footer", typeof(UIElement), typeof(SidebarView), new PropertyMetadata(null)); + + public bool IsPaneOpen + { + get { return (bool)GetValue(IsPaneOpenProperty); } + set { SetValue(IsPaneOpenProperty, value); } + } + public static readonly DependencyProperty IsPaneOpenProperty = + DependencyProperty.Register(nameof(IsPaneOpen), typeof(bool), typeof(SidebarView), new PropertyMetadata(false, OnPropertyChanged)); + + public double OpenPaneLength + { + get { return (double)GetValue(OpenPaneLengthProperty); } + set + { + SetValue(OpenPaneLengthProperty, value); + NegativeOpenPaneLength = -value; + } + } + public static readonly DependencyProperty OpenPaneLengthProperty = + DependencyProperty.Register(nameof(OpenPaneLength), typeof(double), typeof(SidebarView), new PropertyMetadata(240d, OnPropertyChanged)); + + public double NegativeOpenPaneLength + { + get { return (double)GetValue(NegativeOpenPaneLengthProperty); } + set { SetValue(NegativeOpenPaneLengthProperty, value); } + } + public static readonly DependencyProperty NegativeOpenPaneLengthProperty = + DependencyProperty.Register(nameof(NegativeOpenPaneLength), typeof(double), typeof(SidebarView), new PropertyMetadata(null)); + + public ISidebarViewModel ViewModel + { + get => (ISidebarViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(ISidebarViewModel), typeof(SidebarView), new PropertyMetadata(null)); + + public ISidebarItemModel SelectedItem + { + get => (ISidebarItemModel)GetValue(SelectedItemProperty); + set + { + SetValue(SelectedItemProperty, value); + } + } + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register(nameof(SelectedItem), typeof(ISidebarItemModel), typeof(SidebarView), new PropertyMetadata(null)); + + public static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (sender is not SidebarView control) return; + + if (e.Property == OpenPaneLengthProperty) + { + control.UpdateOpenPaneLengthColumn(); + } + else if (e.Property == DisplayModeProperty) + { + control.UpdateDisplayMode(); + } + else if (e.Property == IsPaneOpenProperty) + { + control.UpdateMinimalMode(); + } + } + } +} diff --git a/src/Files.App/UserControls/SideBar/SideBarView.xaml b/src/Files.App/UserControls/SideBar/SideBarView.xaml new file mode 100644 index 000000000000..7f9cc8d5c8ce --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarView.xaml @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App/UserControls/SideBar/SideBarView.xaml.cs b/src/Files.App/UserControls/SideBar/SideBarView.xaml.cs new file mode 100644 index 000000000000..99109b1716f7 --- /dev/null +++ b/src/Files.App/UserControls/SideBar/SideBarView.xaml.cs @@ -0,0 +1,244 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Markup; +using Windows.Foundation; +using Windows.System; +using Windows.UI.Core; + +namespace Files.App.UserControls.Sidebar +{ + [ContentProperty(Name = "InnerContent")] + public sealed partial class SidebarView : UserControl, INotifyPropertyChanged + { + + private const double COMPACT_MAX_WIDTH = 200; + + public event EventHandler? ItemDropped; + public event EventHandler? ItemDragOver; + public event EventHandler? ItemInvoked; + public event EventHandler? ItemContextInvoked; + public event PropertyChangedEventHandler? PropertyChanged; + + internal SidebarItem? SelectedItemContainer = null; + + private bool draggingSidebarResizer; + private double preManipulationSidebarWidth = 0; + + public SidebarView() + { + InitializeComponent(); + } + + internal void UpdateSelectedItemContainer(SidebarItem container) + { + SelectedItemContainer = container; + } + + internal void RaiseItemInvoked(SidebarItem item) + { + // Only leaves can be selected + if (item.Item is null || item.IsGroupHeader) return; + + SelectedItem = item.Item; + ItemInvoked?.Invoke(item, item.Item); + ViewModel.HandleItemInvoked(item.Item); + } + + internal void RaiseContextRequested(SidebarItem item, Point e) + { + ItemContextInvoked?.Invoke(item, new ItemContextInvokedArgs(item.Item, e)); + ViewModel.HandleItemContextInvoked(item, new ItemContextInvokedArgs(item.Item, e)); + } + + internal void RaiseItemDropped(SidebarItem sideBarItem, SidebarItemDropPosition dropPosition, DragEventArgs rawEvent) + { + if (sideBarItem.Item is null) return; + ItemDropped?.Invoke(sideBarItem, new ItemDroppedEventArgs(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent)); + ViewModel.HandleItemDropped(new ItemDroppedEventArgs(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent)); + } + + internal void RaiseItemDragOver(SidebarItem sideBarItem, SidebarItemDropPosition dropPosition, DragEventArgs rawEvent) + { + if (sideBarItem.Item is null) return; + ItemDragOver?.Invoke(sideBarItem, new ItemDragOverEventArgs(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent)); + ViewModel.HandleItemDragOver(new ItemDragOverEventArgs(sideBarItem.Item, rawEvent.DataView, dropPosition, rawEvent)); + } + + private void UpdateMinimalMode() + { + if (DisplayMode != SidebarDisplayMode.Minimal) return; + + if (IsPaneOpen) + { + VisualStateManager.GoToState(this, "MinimalExpanded", true); + } + else + { + VisualStateManager.GoToState(this, "MinimalCollapsed", true); + } + } + + private void UpdateDisplayMode() + { + switch (DisplayMode) + { + case SidebarDisplayMode.Compact: + VisualStateManager.GoToState(this, "Compact", true); + return; + case SidebarDisplayMode.Expanded: + VisualStateManager.GoToState(this, "Expanded", true); + return; + case SidebarDisplayMode.Minimal: + IsPaneOpen = false; + UpdateMinimalMode(); + return; + } + } + + private void UpdateDisplayModeForPaneWidth(double newPaneWidth) + { + if (newPaneWidth < COMPACT_MAX_WIDTH) + { + DisplayMode = SidebarDisplayMode.Compact; + } + else if (newPaneWidth > COMPACT_MAX_WIDTH) + { + DisplayMode = SidebarDisplayMode.Expanded; + OpenPaneLength = newPaneWidth; + } + } + + private void UpdateOpenPaneLengthColumn() + { + DisplayColumn.Width = new GridLength(OpenPaneLength); + } + + private void SidebarView_Loaded(object sender, RoutedEventArgs e) + { + UpdateDisplayMode(); + UpdateOpenPaneLengthColumn(); + PaneColumnGrid.Translation = new System.Numerics.Vector3(0, 0, 32); + } + + private void SidebarResizer_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) + { + draggingSidebarResizer = true; + preManipulationSidebarWidth = PaneColumnGrid.ActualWidth; + VisualStateManager.GoToState(this, "ResizerPressed", true); + e.Handled = true; + } + + private void SidebarResizer_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var newWidth = preManipulationSidebarWidth + e.Cumulative.Translation.X; + UpdateDisplayModeForPaneWidth(newWidth); + e.Handled = true; + } + + private void SidebarResizerControl_KeyDown(object sender, KeyRoutedEventArgs e) + { + var primaryInvocation = e.Key == VirtualKey.Space || e.Key == VirtualKey.Enter; + if (DisplayMode == SidebarDisplayMode.Expanded) + { + if (primaryInvocation) + { + DisplayMode = SidebarDisplayMode.Compact; + return; + } + + var ctrl = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control); + var increment = ctrl.HasFlag(CoreVirtualKeyStates.Down) ? 5 : 1; + + // Left makes the pane smaller so we invert the increment + if (e.Key == VirtualKey.Left) + { + increment = -increment; + } + var newWidth = OpenPaneLength + increment; + UpdateDisplayModeForPaneWidth(newWidth); + e.Handled = true; + return; + } + else if (DisplayMode == SidebarDisplayMode.Compact) + { + if (primaryInvocation || e.Key == VirtualKey.Right) + { + DisplayMode = SidebarDisplayMode.Expanded; + e.Handled = true; + } + } + } + + private void PaneLightDismissLayer_PointerPressed(object sender, PointerRoutedEventArgs e) + { + IsPaneOpen = false; + e.Handled = true; + } + + private void PaneLightDismissLayer_Tapped(object sender, TappedRoutedEventArgs e) + { + IsPaneOpen = false; + e.Handled = true; + } + + private void SidebarResizer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) + { + if (DisplayMode == SidebarDisplayMode.Expanded) + { + DisplayMode = SidebarDisplayMode.Compact; + e.Handled = true; + } + else + { + DisplayMode = SidebarDisplayMode.Expanded; + e.Handled = true; + } + } + + private void SidebarResizer_PointerEntered(object sender, PointerRoutedEventArgs e) + { + var sidebarResizer = (FrameworkElement)sender; + sidebarResizer.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast)); + VisualStateManager.GoToState(this, "ResizerPointerOver", true); + e.Handled = true; + } + + private void SidebarResizer_PointerExited(object sender, PointerRoutedEventArgs e) + { + if (draggingSidebarResizer) + return; + + var sidebarResizer = (FrameworkElement)sender; + sidebarResizer.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Arrow)); + VisualStateManager.GoToState(this, "ResizerNormal", true); + e.Handled = true; + } + + private void SidebarResizer_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + draggingSidebarResizer = false; + VisualStateManager.GoToState(this, "ResizerNormal", true); + e.Handled = true; + } + + private void PaneColumnGrid_ContextRequested(UIElement sender, ContextRequestedEventArgs e) + { + var newArgs = new ItemContextInvokedArgs(null, e.TryGetPosition(this, out var point) ? point : default); + ViewModel.HandleItemContextInvoked(this, newArgs); + e.Handled = true; + } + + private void MenuItemsHost_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if(args.Element is SidebarItem sidebarItem) + { + sidebarItem.HandleItemChange(); + } + } + } +} diff --git a/src/Files.App/UserControls/Sidebar/ISidebarItemModel.cs b/src/Files.App/UserControls/Sidebar/ISidebarItemModel.cs new file mode 100644 index 000000000000..1a18d58bf1e4 --- /dev/null +++ b/src/Files.App/UserControls/Sidebar/ISidebarItemModel.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml.Controls; + +namespace Files.App.UserControls.Sidebar +{ + public interface ISidebarItemModel : INotifyPropertyChanged + { + /// + /// The children of this item that will be rendered as child elements of the SidebarItem + /// + object? Children { get; } + + /// + /// The icon source used to generate the icon for the SidebarItem + /// + IconSource? IconSource { get; } + + /// + /// Determines whether the SidebarItem is expanded and the children are visible + /// or if it is collapsed and children are not visible. + /// + bool IsExpanded { get; set; } + + /// + /// The text of this item that will be rendered as the label of the SidebarItem + /// + string Text { get; } + + /// + /// The tooltip used when hovering over this item in the sidebar + /// + object ToolTip { get; } + } +} diff --git a/src/Files.App/UserControls/Sidebar/SidebarItemAutomationPeer.cs b/src/Files.App/UserControls/Sidebar/SidebarItemAutomationPeer.cs new file mode 100644 index 000000000000..4a981d0ea8b1 --- /dev/null +++ b/src/Files.App/UserControls/Sidebar/SidebarItemAutomationPeer.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using CommunityToolkit.WinUI.UI; +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Automation.Provider; + +namespace Files.App.UserControls.Sidebar +{ + public class SidebarItemAutomationPeer : FrameworkElementAutomationPeer, IInvokeProvider, IExpandCollapseProvider, ISelectionItemProvider + { + public ExpandCollapseState ExpandCollapseState + { + get + { + if (Owner.HasChildren) + return Owner.IsExpanded ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; + return ExpandCollapseState.LeafNode; + } + } + public bool IsSelected => Owner.IsSelected; + public IRawElementProviderSimple SelectionContainer => ProviderFromPeer(CreatePeerForElement(Owner.Owner)); + + private new SidebarItem Owner { get; init; } + + public SidebarItemAutomationPeer(SidebarItem owner) : base(owner) + { + this.Owner = owner; + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ListItem; + } + + protected override string GetNameCore() + { + return Owner.Item?.Text ?? ""; + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.Invoke || patternInterface == PatternInterface.SelectionItem) + { + return this; + } + else if (patternInterface == PatternInterface.ExpandCollapse) + { + if (Owner.CollapseEnabled) + { + return this; + } + } + return base.GetPatternCore(patternInterface); + } + + public void Collapse() + { + if (Owner.CollapseEnabled) + { + Owner.IsExpanded = false; + } + } + + public void Expand() + { + + if (Owner.CollapseEnabled) + { + Owner.IsExpanded = true; + } + } + + public void Invoke() + { + Owner.RaiseItemInvoked(); + } + + public void AddToSelection() + { + Owner.Select(); + } + + public void RemoveFromSelection() + { + // Intentionally left blank + } + + public void Select() + { + Owner.Select(); + } + + protected override int GetSizeOfSetCore() + { + return GetOwnerCollection().Count; + } + + protected override int GetPositionInSetCore() + { + return GetOwnerCollection().IndexOf(Owner.DataContext) + 1; + } + + private IList GetOwnerCollection() + { + if (Owner.FindAscendant() is SidebarItem parent && parent.Item?.Children is IList list) + { + return list; + } + if (Owner?.Owner is not null && Owner.Owner.ViewModel.SidebarItems is IList items) + { + return items; + } + return new List(); + } + } +} diff --git a/src/Files.App/UserControls/Sidebar/SidebarItemDropPosition.cs b/src/Files.App/UserControls/Sidebar/SidebarItemDropPosition.cs new file mode 100644 index 000000000000..d9fc1d760def --- /dev/null +++ b/src/Files.App/UserControls/Sidebar/SidebarItemDropPosition.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.UserControls.Sidebar +{ + /// + /// The position of the item that was dropped on the sidebar item. + /// + public enum SidebarItemDropPosition + { + /// + /// The item was dropped on the top of the sidebar item indicating it should be moved/inserted above this item. + /// + Top, + /// + /// The item was dropped on the bottom of the sidebar item indicating it should be moved/inserted below this item. + /// + Bottom, + /// + /// The item was dropped on the center of the sidebar item indicating it should be moved/inserted as a child of this item. + /// + Center + } +} diff --git a/src/Files.App/UserControls/Sidebar/SidebarViewAutomationPeer.cs b/src/Files.App/UserControls/Sidebar/SidebarViewAutomationPeer.cs new file mode 100644 index 000000000000..279782dc62cd --- /dev/null +++ b/src/Files.App/UserControls/Sidebar/SidebarViewAutomationPeer.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Automation.Provider; + +namespace Files.App.UserControls.Sidebar +{ + class SidebarViewAutomationPeer : FrameworkElementAutomationPeer, ISelectionProvider + { + public bool CanSelectMultiple => false; + public bool IsSelectionRequired => true; + + private new SidebarView Owner { get; init; } + + public SidebarViewAutomationPeer(SidebarView owner) : base(owner) + { + Owner = owner; + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.Selection) + { + return this; + } + return base.GetPatternCore(patternInterface); + } + + public IRawElementProviderSimple[] GetSelection() + { + if (Owner.SelectedItemContainer != null) + return new IRawElementProviderSimple[] + { + ProviderFromPeer(CreatePeerForElement(Owner.SelectedItemContainer)) + }; + return Array.Empty(); + } + } +} diff --git a/src/Files.App/UserControls/SidebarControl.xaml b/src/Files.App/UserControls/SidebarControl.xaml deleted file mode 100644 index d38185de26b7..000000000000 --- a/src/Files.App/UserControls/SidebarControl.xaml +++ /dev/null @@ -1,1196 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - 0,0,0,0 - 0,0,0,0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Files.App/UserControls/SidebarControl.xaml.cs b/src/Files.App/UserControls/SidebarControl.xaml.cs deleted file mode 100644 index 7bd3d87661e5..000000000000 --- a/src/Files.App/UserControls/SidebarControl.xaml.cs +++ /dev/null @@ -1,1099 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -using CommunityToolkit.WinUI.UI; -using Files.App.Helpers.ContextFlyouts; -using Files.App.Services; -using Files.App.ViewModels.Dialogs; -using Files.Core.Storage; -using Files.Core.Storage.Extensions; -using Microsoft.UI.Input; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Input; -using System.Runtime.CompilerServices; -using System.Windows.Input; -using Windows.ApplicationModel.DataTransfer; -using Windows.ApplicationModel.DataTransfer.DragDrop; -using Windows.System; -using Windows.UI.Core; -using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; - -namespace Files.App.UserControls -{ - public sealed partial class SidebarControl : NavigationView, INotifyPropertyChanged - { - private readonly IUserSettingsService userSettingsService; - - private readonly ICommandManager commands; - - private readonly IFileTagsService _fileTagsService; - public IQuickAccessService QuickAccessService { get; } - - public delegate void SelectedTagChangedEventHandler(object sender, SelectedTagChangedEventArgs e); - - public static event SelectedTagChangedEventHandler? SelectedTagChanged; - - public delegate void SidebarItemInvokedEventHandler(object sender, SidebarItemInvokedEventArgs e); - - public event SidebarItemInvokedEventHandler SidebarItemInvoked; - - public delegate void SidebarItemNewPaneInvokedEventHandler(object sender, SidebarItemNewPaneInvokedEventArgs e); - - public event SidebarItemNewPaneInvokedEventHandler SidebarItemNewPaneInvoked; - - public delegate void SidebarItemPropertiesInvokedEventHandler(object sender, SidebarItemPropertiesInvokedEventArgs e); - - public event SidebarItemPropertiesInvokedEventHandler SidebarItemPropertiesInvoked; - - public delegate void SidebarItemDroppedEventHandler(object sender, SidebarItemDroppedEventArgs e); - - public event SidebarItemDroppedEventHandler SidebarItemDropped; - - private INavigationControlItem rightClickedItem; - - private object? dragOverSection, dragOverItem = null; - - private bool isDropOnProcess = false; - - /// - /// true if the user is currently resizing the sidebar - /// - private bool dragging; - - private double originalSize = 0; - - private bool lockFlag = false; - - public SidebarPinnedModel SidebarPinnedModel => App.QuickAccessManager.Model; - - // Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc... - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(SidebarViewModel), typeof(SidebarControl), new PropertyMetadata(null)); - - public static readonly DependencyProperty SelectedSidebarItemProperty = DependencyProperty.Register(nameof(SelectedSidebarItem), typeof(INavigationControlItem), typeof(SidebarControl), new PropertyMetadata(null)); - - public INavigationControlItem SelectedSidebarItem - { - get => (INavigationControlItem)GetValue(SelectedSidebarItemProperty); - set - { - if (IsLoaded) - SetValue(SelectedSidebarItemProperty, value); - } - } - - public static readonly DependencyProperty TabContentProperty = DependencyProperty.Register(nameof(TabContent), typeof(UIElement), typeof(SidebarControl), new PropertyMetadata(null)); - - public UIElement TabContent - { - get => (UIElement)GetValue(TabContentProperty); - set => SetValue(TabContentProperty, value); - } - - public readonly ICommand CreateLibraryCommand = new AsyncRelayCommand(LibraryManager.ShowCreateNewLibraryDialog); - - public readonly ICommand RestoreLibrariesCommand = new AsyncRelayCommand(LibraryManager.ShowRestoreDefaultLibrariesDialog); - - private ICommand HideSectionCommand { get; } - - private ICommand PinItemCommand { get; } - - private ICommand UnpinItemCommand { get; } - - private ICommand OpenInNewTabCommand { get; } - - private ICommand OpenInNewWindowCommand { get; } - - private ICommand OpenInNewPaneCommand { get; } - - private ICommand EjectDeviceCommand { get; } - - private ICommand FormatDriveCommand { get; } - - private ICommand OpenPropertiesCommand { get; } - - private ICommand ReorderItemsCommand { get; } - - private bool IsInPointerPressed = false; - - private readonly DispatcherQueueTimer dragOverSectionTimer, dragOverItemTimer; - - public SidebarControl() - { - userSettingsService = Ioc.Default.GetRequiredService(); - commands = Ioc.Default.GetRequiredService(); - _fileTagsService = Ioc.Default.GetRequiredService(); - QuickAccessService = Ioc.Default.GetRequiredService(); - - InitializeComponent(); - - dragOverSectionTimer = DispatcherQueue.CreateTimer(); - dragOverItemTimer = DispatcherQueue.CreateTimer(); - - HideSectionCommand = new RelayCommand(HideSection); - UnpinItemCommand = new RelayCommand(UnpinItem); - PinItemCommand = new RelayCommand(PinItem); - OpenInNewTabCommand = new AsyncRelayCommand(OpenInNewTab); - OpenInNewWindowCommand = new AsyncRelayCommand(OpenInNewWindow); - OpenInNewPaneCommand = new AsyncRelayCommand(OpenInNewPane); - EjectDeviceCommand = new AsyncRelayCommand(EjectDevice); - FormatDriveCommand = new RelayCommand(FormatDrive); - OpenPropertiesCommand = new RelayCommand(OpenProperties); - ReorderItemsCommand = new AsyncRelayCommand(ReorderItems); - } - - public SidebarViewModel ViewModel - { - get => (SidebarViewModel)GetValue(ViewModelProperty); - set => SetValue(ViewModelProperty, value); - } - - private bool canOpenInNewPane; - - public bool CanOpenInNewPane - { - get => canOpenInNewPane; - set - { - if (value != canOpenInNewPane) - { - canOpenInNewPane = value; - NotifyPropertyChanged(nameof(CanOpenInNewPane)); - } - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - - private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - private List GetLocationItemMenuItems(INavigationControlItem item, CommandBarFlyout menu) - { - var options = item.MenuOptions; - - var favoriteModel = App.QuickAccessManager.Model; - var favoriteIndex = favoriteModel.IndexOfItem(item); - var favoriteCount = favoriteModel.FavoriteItems.Count; - - var isFavoriteItem = item.Section is SectionType.Favorites && favoriteIndex is not -1; - var showMoveItemUp = isFavoriteItem && favoriteIndex > 0; - var showMoveItemDown = isFavoriteItem && favoriteIndex < favoriteCount - 1; - - var isDriveItem = item is DriveItem; - var isDriveItemPinned = isDriveItem && ((DriveItem)item).IsPinned; - - var isTagItem = item is FileTagItem; - - return new List() - { - new ContextMenuFlyoutItemViewModel() - { - Text = "SideBarCreateNewLibrary/Text".GetLocalizedResource(), - Glyph = "\uE710", - Command = CreateLibraryCommand, - ShowItem = options.IsLibrariesHeader - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "SideBarRestoreLibraries/Text".GetLocalizedResource(), - Glyph = "\uE10E", - Command = RestoreLibrariesCommand, - ShowItem = options.IsLibrariesHeader - }, - new ContextMenuFlyoutItemViewModelBuilder(commands.EmptyRecycleBin) - { - IsVisible = options.ShowEmptyRecycleBin, - }.Build(), - new ContextMenuFlyoutItemViewModelBuilder(commands.RestoreAllRecycleBin) - { - IsVisible = options.ShowEmptyRecycleBin, - }.Build(), - new ContextMenuFlyoutItemViewModel() - { - Text = "OpenInNewTab".GetLocalizedResource(), - OpacityIcon = new OpacityIconModel() - { - OpacityIconStyle = "ColorIconOpenInNewTab", - }, - Command = OpenInNewTabCommand, - ShowItem = options.IsLocationItem && userSettingsService.GeneralSettingsService.ShowOpenInNewTab - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "OpenInNewWindow".GetLocalizedResource(), - OpacityIcon = new OpacityIconModel() - { - OpacityIconStyle = "ColorIconOpenInNewWindow", - }, - Command = OpenInNewWindowCommand, - ShowItem = options.IsLocationItem && userSettingsService.GeneralSettingsService.ShowOpenInNewTab - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "OpenInNewPane".GetLocalizedResource(), - Command = OpenInNewPaneCommand, - ShowItem = options.IsLocationItem && userSettingsService.GeneralSettingsService.ShowOpenInNewPane - }, - new ContextMenuFlyoutItemViewModelBuilder(commands.OpenAllTaggedItems) - { - IsVisible = isTagItem - }.Build(), - new ContextMenuFlyoutItemViewModel() - { - Text = "PinToFavorites".GetLocalizedResource(), - OpacityIcon = new OpacityIconModel() - { - OpacityIconStyle = "ColorIconPinToFavorites", - }, - Command = PinItemCommand, - ShowItem = isDriveItem && !isDriveItemPinned - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "UnpinFromFavorites".GetLocalizedResource(), - OpacityIcon = new OpacityIconModel() - { - OpacityIconStyle = "ColorIconUnpinFromFavorites", - }, - Command = UnpinItemCommand, - ShowItem = options.ShowUnpinItem || isDriveItemPinned - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "ReorderSidebarItemsDialogText".GetLocalizedResource(), - Glyph = "\uE8D8", - Command = ReorderItemsCommand, - ShowItem = isFavoriteItem || item.Section is SectionType.Favorites - }, - new ContextMenuFlyoutItemViewModel() - { - Text = string.Format("SideBarHideSectionFromSideBar/Text".GetLocalizedResource(), rightClickedItem.Text), - Glyph = "\uE77A", - Command = HideSectionCommand, - ShowItem = options.ShowHideSection - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "SideBarEjectDevice/Text".GetLocalizedResource(), - Command = EjectDeviceCommand, - ShowItem = options.ShowEjectDevice - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "FormatDriveText".GetLocalizedResource(), - Command = FormatDriveCommand, - CommandParameter = item, - ShowItem = options.ShowFormatDrive - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "Properties".GetLocalizedResource(), - OpacityIcon = new OpacityIconModel() - { - OpacityIconStyle = "ColorIconProperties", - }, - Command = OpenPropertiesCommand, - CommandParameter = menu, - ShowItem = options.ShowProperties - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "TurnOnBitLocker".GetLocalizedResource(), - Tag = "TurnOnBitLockerPlaceholder", - ShowItem = isDriveItem, - IsEnabled = false - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "ManageBitLocker".GetLocalizedResource(), - Tag = "ManageBitLockerPlaceholder", - ShowItem = isDriveItem, - IsEnabled = false - }, - new ContextMenuFlyoutItemViewModel() - { - ItemType = ContextMenuFlyoutItemType.Separator, - Tag = "OverflowSeparator", - IsHidden = !options.ShowShellItems, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "Loading".GetLocalizedResource(), - Glyph = "\xE712", - Items = new List(), - ID = "ItemOverflow", - Tag = "ItemOverflow", - IsEnabled = false, - IsHidden = !options.ShowShellItems, - } - }.Where(x => x.ShowItem).ToList(); - } - - private void HideSection() - { - switch (rightClickedItem.Section) - { - case SectionType.Favorites: - userSettingsService.GeneralSettingsService.ShowFavoritesSection = false; - break; - case SectionType.Library: - userSettingsService.GeneralSettingsService.ShowLibrarySection = false; - break; - case SectionType.CloudDrives: - userSettingsService.GeneralSettingsService.ShowCloudDrivesSection = false; - break; - case SectionType.Drives: - userSettingsService.GeneralSettingsService.ShowDrivesSection = false; - break; - case SectionType.Network: - userSettingsService.GeneralSettingsService.ShowNetworkDrivesSection = false; - break; - case SectionType.WSL: - userSettingsService.GeneralSettingsService.ShowWslSection = false; - break; - case SectionType.FileTag: - userSettingsService.GeneralSettingsService.ShowFileTagsSection = false; - break; - } - } - - private async Task ReorderItems() - { - var dialog = new ReorderSidebarItemsDialogViewModel(); - var dialogService = Ioc.Default.GetRequiredService(); - var result = await dialogService.ShowDialogAsync(dialog); - } - - private async Task OpenInNewPane() - { - if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) - return; - - SidebarItemNewPaneInvoked?.Invoke(this, new SidebarItemNewPaneInvokedEventArgs(rightClickedItem)); - } - - private async Task OpenInNewTab() - { - if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) - return; - - await NavigationHelpers.OpenPathInNewTab(rightClickedItem.Path); - } - - private async Task OpenInNewWindow() - { - if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) - return; - - await NavigationHelpers.OpenPathInNewWindowAsync(rightClickedItem.Path); - } - - private void PinItem() - { - if (rightClickedItem is DriveItem) - _ = QuickAccessService.PinToSidebar(new[] { rightClickedItem.Path }); - } - private void UnpinItem() - { - if (rightClickedItem.Section == SectionType.Favorites || rightClickedItem is DriveItem) - _ = QuickAccessService.UnpinFromSidebar(rightClickedItem.Path); - } - - private void OpenProperties(CommandBarFlyout menu) - { - EventHandler flyoutClosed = null!; - flyoutClosed = (s, e) => - { - menu.Closed -= flyoutClosed; - SidebarItemPropertiesInvoked?.Invoke(this, new SidebarItemPropertiesInvokedEventArgs(rightClickedItem)); - }; - menu.Closed += flyoutClosed; - } - - private async Task EjectDevice() - { - var result = await DriveHelpers.EjectDeviceAsync(rightClickedItem.Path); - await UIHelpers.ShowDeviceEjectResultAsync(rightClickedItem is DriveItem driveItem ? driveItem.Type : Data.Items.DriveType.Unknown, result); - } - - private void FormatDrive() - { - Win32API.OpenFormatDriveDialog(rightClickedItem.Path); - } - - private async void Sidebar_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) - { - try - { - if (args.InvokedItem is null || args.InvokedItemContainer is null) - return; - - var navigationPath = args.InvokedItemContainer.Tag?.ToString(); - - if (await DriveHelpers.CheckEmptyDrive(navigationPath)) - return; - - var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); - if ((IsInPointerPressed || ctrlPressed) && navigationPath is not null) - { - await NavigationHelpers.OpenPathInNewTab(navigationPath); - return; - } - - SidebarItemInvoked?.Invoke(this, new SidebarItemInvokedEventArgs(args.InvokedItemContainer)); - } - finally - { - IsInPointerPressed = false; - } - } - - private async void Sidebar_PointerPressed(object sender, PointerRoutedEventArgs e) - { - var properties = e.GetCurrentPoint(null).Properties; - var context = (sender as NavigationViewItem)?.DataContext; - - if (!properties.IsMiddleButtonPressed || context is not INavigationControlItem item || await DriveHelpers.CheckEmptyDrive(item?.Path)) - return; - - IsInPointerPressed = true; - e.Handled = true; - } - - private void PaneRoot_RightTapped(object sender, RightTappedRoutedEventArgs e) - { - var contextMenu = FlyoutBase.GetAttachedFlyout(this); - contextMenu.ShowAt(this, new FlyoutShowOptions() { Position = e.GetPosition(this) }); - - e.Handled = true; - } - - private async void NavigationViewItem_RightTapped(object sender, RightTappedRoutedEventArgs e) - { - var itemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full }; - itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; - if (sender is not NavigationViewItem sidebarItem || - sidebarItem.DataContext is not INavigationControlItem item) - return; - - if (item is FileTagItem tagItem) - { - var cts = new CancellationTokenSource(); - var items = new List<(string path, bool isFolder)>(); - - await foreach (var taggedItem in _fileTagsService.GetItemsForTagAsync(tagItem.FileTag.Uid, cts.Token)) - { - items.Add(( - taggedItem.Storable.TryGetPath() ?? string.Empty, - taggedItem.Storable is IFolder)); - } - - SelectedTagChanged?.Invoke(this, new SelectedTagChangedEventArgs(items)); - } - - rightClickedItem = item; - - var menuItems = GetLocationItemMenuItems(item, itemContextMenuFlyout); - var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); - - secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); - itemContextMenuFlyout.ShowAt(sidebarItem, new FlyoutShowOptions { Position = e.GetPosition(sidebarItem) }); - - if (item.MenuOptions.ShowShellItems) - _ = ShellContextmenuHelper.LoadShellMenuItems(rightClickedItem.Path, itemContextMenuFlyout, item.MenuOptions); - - e.Handled = true; - } - - private void NavigationViewItem_DragEnter(object sender, DragEventArgs e) - { - var navView = sender as NavigationViewItem; - VisualStateManager.GoToState(navView, "DragEnter", false); - - if (navView?.DataContext is not INavigationControlItem iNavItem) - return; - - if (string.IsNullOrEmpty(iNavItem.Path)) - HandleDragOverSection(sender); - else - HandleDragOverItem(sender); - } - - private void HandleDragOverItem(object sender) - { - dragOverItem = sender; - dragOverItemTimer.Stop(); - dragOverItemTimer.Debounce(() => - { - if (dragOverItem is null) - return; - - dragOverItemTimer.Stop(); - SidebarItemInvoked?.Invoke(this, new SidebarItemInvokedEventArgs(dragOverItem as NavigationViewItemBase)); - dragOverItem = null; - }, TimeSpan.FromMilliseconds(1000), false); - } - - private void HandleDragOverSection(object sender) - { - dragOverSection = sender; - dragOverSectionTimer.Stop(); - dragOverSectionTimer.Debounce(() => - { - if (dragOverSection is null) - return; - - dragOverSectionTimer.Stop(); - if ((dragOverSection as NavigationViewItem)?.DataContext is LocationItem section) - section.IsExpanded = true; - - dragOverSection = null; - }, TimeSpan.FromMilliseconds(1000), false); - } - - private void NavigationViewItem_DragLeave(object sender, DragEventArgs e) - { - var navView = sender as NavigationViewItem; - VisualStateManager.GoToState(navView, "DragLeave", false); - - isDropOnProcess = false; - - if (navView?.DataContext is not INavigationControlItem) - return; - - if (sender == dragOverItem) - dragOverItem = null; // Reset dragged over item - - if (sender == dragOverSection) - dragOverSection = null; // Reset dragged over item - } - - private async void NavigationViewLocationItem_DragOver(object sender, DragEventArgs e) - { - if ((sender as NavigationViewItem)?.DataContext is not LocationItem locationItem) - return; - - var deferral = e.GetDeferral(); - - if (FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - e.Handled = true; - isDropOnProcess = true; - - var isPathNull = string.IsNullOrEmpty(locationItem.Path); - var storageItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView); - var hasStorageItems = storageItems.Any(); - - if (isPathNull && hasStorageItems && SectionType.Favorites.Equals(locationItem.Section)) - { - var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.FavoriteItems.Contains(item.Path)); - - if (!haveFoldersToPin) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else - { - var captionText = "PinToFavorites".GetLocalizedResource(); - CompleteDragEventArgs(e, captionText, DataPackageOperation.Move); - } - } - else if (isPathNull || - (hasStorageItems && storageItems.AreItemsAlreadyInFolder(locationItem.Path)) || - locationItem.Path.StartsWith("Home", StringComparison.OrdinalIgnoreCase)) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else if (hasStorageItems is false) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else - { - string captionText; - DataPackageOperation operationType; - if (locationItem.Path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) - { - captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Move; - } - else if (e.Modifiers.HasFlag(DragDropModifiers.Alt) || e.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift)) - { - captionText = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Link; - } - else if (e.Modifiers.HasFlag(DragDropModifiers.Control)) - { - captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Copy; - } - else if (e.Modifiers.HasFlag(DragDropModifiers.Shift)) - { - captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Move; - } - else if (storageItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) - || ZipStorageFolder.IsZipPath(locationItem.Path)) - { - captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Copy; - } - else if (locationItem.IsDefaultLocation || storageItems.AreItemsInSameDrive(locationItem.Path)) - { - captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Move; - } - else - { - captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); - operationType = DataPackageOperation.Copy; - } - CompleteDragEventArgs(e, captionText, operationType); - } - } - - deferral.Complete(); - } - - private DragEventArgs CompleteDragEventArgs(DragEventArgs e, string captionText, DataPackageOperation operationType) - { - e.DragUIOverride.IsCaptionVisible = true; - e.DragUIOverride.Caption = captionText; - e.AcceptedOperation = operationType; - return e; - } - - private async void NavigationViewLocationItem_Drop(object sender, DragEventArgs e) - { - if (lockFlag) - return; - - lockFlag = true; - - dragOverItem = null; // Reset dragged over item - dragOverSection = null; // Reset dragged over section - - if (sender is not NavigationViewItem navView || navView.DataContext is not LocationItem locationItem) - return; - - // If the dropped item is a folder or file from a file system - if (FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - { - VisualStateManager.GoToState(navView, "Drop", false); - - var deferral = e.GetDeferral(); - - if (string.IsNullOrEmpty(locationItem.Path) && isDropOnProcess && SectionType.Favorites.Equals(locationItem.Section)) // Pin to Favorites section - { - var storageItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView); - foreach (var item in storageItems) - { - if (item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.FavoriteItems.Contains(item.Path)) - await QuickAccessService.PinToSidebar(item.Path); - } - } - else - { - var signal = new AsyncManualResetEvent(); - SidebarItemDropped?.Invoke(this, new SidebarItemDroppedEventArgs() - { - Package = e.DataView, - ItemPath = locationItem.Path, - AcceptedOperation = e.AcceptedOperation, - SignalEvent = signal - }); - await signal.WaitAsync(); - } - - isDropOnProcess = false; - deferral.Complete(); - } - - await Task.Yield(); - lockFlag = false; - } - - private async void NavigationViewDriveItem_DragOver(object sender, DragEventArgs e) - { - if ((sender as NavigationViewItem)?.DataContext is not DriveItem driveItem || - !FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - return; - - var deferral = e.GetDeferral(); - e.Handled = true; - - var storageItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView); - var hasStorageItems = storageItems.Any(); - - if ("Unknown".GetLocalizedResource().Equals(driveItem.SpaceText, StringComparison.OrdinalIgnoreCase) || - (hasStorageItems && storageItems.AreItemsAlreadyInFolder(driveItem.Path))) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else if (!hasStorageItems) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else - { - string captionText; - DataPackageOperation operationType; - if (e.Modifiers.HasFlag(DragDropModifiers.Alt) || e.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift)) - { - captionText = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), driveItem.Text); - operationType = DataPackageOperation.Link; - } - else if (e.Modifiers.HasFlag(DragDropModifiers.Control)) - { - captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), driveItem.Text); - operationType = DataPackageOperation.Copy; - } - else if (e.Modifiers.HasFlag(DragDropModifiers.Shift)) - { - captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), driveItem.Text); - operationType = DataPackageOperation.Move; - } - else if (storageItems.AreItemsInSameDrive(driveItem.Path)) - { - captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), driveItem.Text); - operationType = DataPackageOperation.Move; - } - else - { - captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), driveItem.Text); - operationType = DataPackageOperation.Copy; - } - CompleteDragEventArgs(e, captionText, operationType); - } - - deferral.Complete(); - } - - private async void NavigationViewDriveItem_Drop(object sender, DragEventArgs e) - { - if (lockFlag) - return; - - lockFlag = true; - - dragOverItem = null; // Reset dragged over item - dragOverSection = null; // Reset dragged over section - - if (sender is not NavigationViewItem navView || navView.DataContext is not DriveItem driveItem) - return; - - VisualStateManager.GoToState(navView, "Drop", false); - - var deferral = e.GetDeferral(); - - var signal = new AsyncManualResetEvent(); - SidebarItemDropped?.Invoke(this, new SidebarItemDroppedEventArgs() - { - Package = e.DataView, - ItemPath = driveItem.Path, - AcceptedOperation = e.AcceptedOperation, - SignalEvent = signal - }); - await signal.WaitAsync(); - - deferral.Complete(); - await Task.Yield(); - lockFlag = false; - } - - private async void NavigationViewFileTagItem_DragOver(object sender, DragEventArgs e) - { - if ((sender as NavigationViewItem)?.DataContext is not FileTagItem fileTagItem || - !FilesystemHelpers.HasDraggedStorageItems(e.DataView)) - return; - - var deferral = e.GetDeferral(); - e.Handled = true; - - var storageItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView); - - if (!storageItems.Any()) - { - e.AcceptedOperation = DataPackageOperation.None; - } - else - { - e.DragUIOverride.IsCaptionVisible = true; - e.DragUIOverride.Caption = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), fileTagItem.Text); - e.AcceptedOperation = DataPackageOperation.Link; - } - - deferral.Complete(); - } - - private async void NavigationViewFileTag_Drop(object sender, DragEventArgs e) - { - if (lockFlag) - return; - - lockFlag = true; - - dragOverItem = null; // Reset dragged over item - dragOverSection = null; // Reset dragged over section - - if (sender is not NavigationViewItem navItem || navItem.DataContext is not FileTagItem fileTagItem) - return; - - VisualStateManager.GoToState(navItem, "Drop", false); - - var deferral = e.GetDeferral(); - - var storageItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView); - foreach (var item in storageItems.Where(x => !string.IsNullOrEmpty(x.Path))) - { - var listedItem = new ListedItem(null) - { - ItemPath = item.Path, - FileFRN = await FileTagsHelper.GetFileFRN(item.Item), - FileTags = new[] { fileTagItem.FileTag.Uid } - }; - } - - deferral.Complete(); - await Task.Yield(); - lockFlag = false; - } - - private void SidebarNavView_Loaded(object sender, RoutedEventArgs e) - { - (this.FindDescendant("TabContentBorder") as Border)!.Child = TabContent; - } - - private void SidebarControl_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) - { - IsPaneToggleButtonVisible = args.DisplayMode == NavigationViewDisplayMode.Minimal; - } - - private void Border_KeyDown(object sender, KeyRoutedEventArgs e) - { - var ctrl = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control); - var step = ctrl.HasFlag(CoreVirtualKeyStates.Down) ? 5 : 1; - originalSize = IsPaneOpen ? userSettingsService.AppearanceSettingsService.SidebarWidth : CompactPaneLength; - - if (e.Key == VirtualKey.Space || e.Key == VirtualKey.Enter) - { - IsPaneOpen = !IsPaneOpen; - return; - } - - if (IsPaneOpen) - { - if (e.Key == VirtualKey.Left) - { - SetSize(-step, true); - e.Handled = true; - } - else if (e.Key == VirtualKey.Right) - { - SetSize(step, true); - e.Handled = true; - } - } - else if (e.Key == VirtualKey.Right) - { - IsPaneOpen = !IsPaneOpen; - return; - } - - userSettingsService.AppearanceSettingsService.SidebarWidth = OpenPaneLength; - } - - private void Border_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) - { - if (DisplayMode == NavigationViewDisplayMode.Expanded) - SetSize(e.Cumulative.Translation.X); - } - - private void Border_PointerExited(object sender, PointerRoutedEventArgs e) - { - if (dragging) - return; // keep showing pressed event if currently resizing the sidebar - - var border = (Border)sender; - border.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Arrow)); - VisualStateManager.GoToState(border.FindAscendant(), "ResizerNormal", true); - } - - private void Border_PointerEntered(object sender, PointerRoutedEventArgs e) - { - if (DisplayMode != NavigationViewDisplayMode.Expanded) - return; - - var border = (Border)sender; - border.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast)); - VisualStateManager.GoToState(border.FindAscendant(), "ResizerPointerOver", true); - } - - private void SetSize(double val, bool closeImmediatelyOnOversize = false) - { - if (IsPaneOpen) - { - var newSize = originalSize + val; - var isNewSizeGreaterThanMinimum = newSize >= Constants.UI.MinimumSidebarWidth; - if (newSize <= Constants.UI.MaximumSidebarWidth && isNewSizeGreaterThanMinimum) - OpenPaneLength = newSize; // passing a negative value will cause an exception - - // if the new size is below the minimum, check whether to toggle the pane collapse the sidebar - IsPaneOpen = !(!isNewSizeGreaterThanMinimum && (Constants.UI.MinimumSidebarWidth + val <= CompactPaneLength || closeImmediatelyOnOversize)); - } - else - { - if (val < Constants.UI.MinimumSidebarWidth - CompactPaneLength && - !closeImmediatelyOnOversize) - return; - - OpenPaneLength = val + CompactPaneLength; // set open sidebar length to minimum value to keep it smooth - IsPaneOpen = true; - } - } - - private void ResizeElementBorder_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) - { - var border = (Border)sender; - border.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Arrow)); - VisualStateManager.GoToState(border.FindAscendant(), "ResizerNormal", true); - userSettingsService.AppearanceSettingsService.SidebarWidth = OpenPaneLength; - dragging = false; - } - - private void ResizeElementBorder_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) - { - IsPaneOpen = !IsPaneOpen; - } - - private void ResizeElementBorder_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) - { - if (DisplayMode != NavigationViewDisplayMode.Expanded) - return; - - originalSize = IsPaneOpen ? userSettingsService.AppearanceSettingsService.SidebarWidth : CompactPaneLength; - var border = (Border)sender; - border.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast)); - VisualStateManager.GoToState(border.FindAscendant(), "ResizerPressed", true); - dragging = true; - } - - public static GridLength GetSidebarCompactSize() - { - return App.Current.Resources.TryGetValue("NavigationViewCompactPaneLength", out object paneLength) && paneLength is double paneLengthDouble - ? new GridLength(paneLengthDouble) - : new GridLength(200); - } - - #region Sidebar sections expanded state management - - private async void NavigationView_Expanding(NavigationView sender, NavigationViewItemExpandingEventArgs args) - { - if (args.ExpandingItem is not LocationItem loc || loc.ChildItems is null) - return; - - await SetNavigationViewCollapse(sender, loc, true); - } - - private async void NavigationView_Collapsed(NavigationView sender, NavigationViewItemCollapsedEventArgs args) - { - if (args.CollapsedItem is not LocationItem loc || loc.ChildItems is null) - return; - - await SetNavigationViewCollapse(sender, loc, false); - } - - private static async Task SetNavigationViewCollapse(NavigationView sender, LocationItem loc, bool isCollapsed) - { - await Task.Delay(50); // Wait a little so IsPaneOpen tells the truth when in minimal mode - if (sender.IsPaneOpen) // Don't store expanded state if sidebar pane is closed - Ioc.Default.GetRequiredService().Set(isCollapsed, $"section:{loc.Text.Replace('\\', '_')}"); - } - - private void NavigationView_PaneOpened(NavigationView sender, object args) - { - // Restore expanded state when pane is opened - foreach (var loc in ViewModel.SideBarItems.OfType().Where(x => x.ChildItems is not null)) - loc.IsExpanded = Ioc.Default.GetRequiredService().Get(loc.Text == "SidebarFavorites".GetLocalizedResource(), $"section:{loc.Text.Replace('\\', '_')}"); - } - - private void NavigationView_PaneClosed(NavigationView sender, object args) - { - // Collapse all sections but do not store the state when pane is closed - foreach (var loc in ViewModel.SideBarItems.OfType().Where(x => x.ChildItems is not null)) - loc.IsExpanded = false; - } - - #endregion - } - - public class SidebarItemDroppedEventArgs : EventArgs - { - public DataPackageView Package { get; set; } - public string ItemPath { get; set; } - public DataPackageOperation AcceptedOperation { get; set; } - public AsyncManualResetEvent SignalEvent { get; set; } - } - - public class SidebarItemInvokedEventArgs : EventArgs - { - public NavigationViewItemBase InvokedItemContainer { get; set; } - - public SidebarItemInvokedEventArgs(NavigationViewItemBase ItemContainer) - { - InvokedItemContainer = ItemContainer; - } - } - - public class SidebarItemPropertiesInvokedEventArgs : EventArgs - { - public object InvokedItemDataContext { get; set; } - - public SidebarItemPropertiesInvokedEventArgs(object invokedItemDataContext) - { - InvokedItemDataContext = invokedItemDataContext; - } - } - - public class SidebarItemNewPaneInvokedEventArgs : EventArgs - { - public object InvokedItemDataContext { get; set; } - - public SidebarItemNewPaneInvokedEventArgs(object invokedItemDataContext) - { - InvokedItemDataContext = invokedItemDataContext; - } - } - - public class NavItemDataTemplateSelector : DataTemplateSelector - { - public DataTemplate LocationNavItemTemplate { get; set; } - public DataTemplate DriveNavItemTemplate { get; set; } - public DataTemplate LinuxNavItemTemplate { get; set; } - public DataTemplate FileTagNavItemTemplate { get; set; } - public DataTemplate HeaderNavItemTemplate { get; set; } - - protected override DataTemplate? SelectTemplateCore(object item) - { - if (item is null || item is not INavigationControlItem navControlItem) - return null; - - return navControlItem.ItemType switch - { - NavigationControlItemType.Location => LocationNavItemTemplate, - NavigationControlItemType.Drive => DriveNavItemTemplate, - NavigationControlItemType.LinuxDistro => LinuxNavItemTemplate, - NavigationControlItemType.FileTag => FileTagNavItemTemplate, - _ => null - }; - } - } -} diff --git a/src/Files.App/Utils/Global/WSLDistroManager.cs b/src/Files.App/Utils/Global/WSLDistroManager.cs index d289e1e86bd8..bdcb043de881 100644 --- a/src/Files.App/Utils/Global/WSLDistroManager.cs +++ b/src/Files.App/Utils/Global/WSLDistroManager.cs @@ -37,7 +37,7 @@ public async Task UpdateDrivesAsync() { Text = folder.DisplayName, Path = folder.Path, - Logo = logoURI, + Icon = logoURI, MenuOptions = new ContextMenuOptions { IsLocationItem = true }, }; diff --git a/src/Files.App/ViewModels/MainPageViewModel.cs b/src/Files.App/ViewModels/MainPageViewModel.cs index 5866f1116a09..67a7018c604c 100644 --- a/src/Files.App/ViewModels/MainPageViewModel.cs +++ b/src/Files.App/ViewModels/MainPageViewModel.cs @@ -225,7 +225,7 @@ public async Task UpdateTabInfo(TabItem tabItem, object navigationArg) else if (App.WSLDistroManager.TryGetDistro(currentPath, out WslDistroItem? wslDistro) && currentPath.Equals(wslDistro.Path)) { tabLocationHeader = wslDistro.Text; - iconSource.ImageSource = new BitmapImage(wslDistro.Logo); + iconSource.ImageSource = new BitmapImage(wslDistro.Icon); } else { diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 92496aa659c3..bafd253284e3 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -1,20 +1,34 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Microsoft.UI.Dispatching; +using Files.App.Helpers.ContextFlyouts; +using Files.App.UserControls.Sidebar; +using Files.App.ViewModels.Dialogs; +using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Media.Imaging; using System.Collections.Specialized; using System.IO; +using System.Windows.Input; +using Windows.ApplicationModel.DataTransfer.DragDrop; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.System; +using Windows.UI.Core; +using Files.Core.Storage; +using Files.Core.Storage.Extensions; +using Files.App.Data.Items; namespace Files.App.ViewModels.UserControls { - public class SidebarViewModel : ObservableObject, IDisposable + public class SidebarViewModel : ObservableObject, IDisposable, ISidebarViewModel { private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - + private ICommandManager Commands { get; } = Ioc.Default.GetRequiredService(); private readonly DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); + private readonly IFileTagsService fileTagsService; private readonly NetworkDrivesViewModel networkDrivesViewModel = Ioc.Default.GetRequiredService(); @@ -25,30 +39,40 @@ public IPaneHolder PaneHolder set => SetProperty(ref paneHolder, value); } + public MenuFlyout PaneFlyout; + public IFilesystemHelpers FilesystemHelpers => PaneHolder?.FilesystemHelpers; - private DispatcherQueue dispatcherQueue; - - public BulkConcurrentObservableCollection SideBarItems { get; init; } + private Microsoft.UI.Dispatching.DispatcherQueue dispatcherQueue; + private INavigationControlItem rightClickedItem; - public static readonly GridLength CompactSidebarWidth = SidebarControl.GetSidebarCompactSize(); + public object SidebarItems => sidebarItems; + public BulkConcurrentObservableCollection sidebarItems { get; init; } + public SidebarPinnedModel SidebarPinnedModel => App.QuickAccessManager.Model; + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); - private NavigationViewDisplayMode sidebarDisplayMode; - public NavigationViewDisplayMode SidebarDisplayMode + private SidebarDisplayMode sidebarDisplayMode; + public SidebarDisplayMode SidebarDisplayMode { get => sidebarDisplayMode; set { + // We only want to track non minimal mode + if (value == SidebarDisplayMode.Minimal) return; if (SetProperty(ref sidebarDisplayMode, value)) { OnPropertyChanged(nameof(IsSidebarCompactSize)); - + IsSidebarOpen = sidebarDisplayMode == SidebarDisplayMode.Expanded; UpdateTabControlMargin(); } } } + public delegate void SelectedTagChangedEventHandler(object sender, SelectedTagChangedEventArgs e); + + public static event SelectedTagChangedEventHandler? SelectedTagChanged; + private readonly SectionType[] SectionOrder = new SectionType[] { @@ -63,7 +87,7 @@ public NavigationViewDisplayMode SidebarDisplayMode }; public bool IsSidebarCompactSize - => SidebarDisplayMode == NavigationViewDisplayMode.Compact || SidebarDisplayMode == NavigationViewDisplayMode.Minimal; + => SidebarDisplayMode == SidebarDisplayMode.Compact || SidebarDisplayMode == SidebarDisplayMode.Minimal; public void NotifyInstanceRelatedPropertiesChanged(string arg) { @@ -77,9 +101,9 @@ public void UpdateSidebarSelectedItemFromArgs(string arg) var value = arg; INavigationControlItem? item = null; - var sidebarItems = SideBarItems + var filteredItems = sidebarItems .Where(x => !string.IsNullOrWhiteSpace(x.Path)) - .Concat(SideBarItems.Where(x => (x as LocationItem)?.ChildItems is not null).SelectMany(x => ((LocationItem)x).ChildItems).Where(x => !string.IsNullOrWhiteSpace(x.Path))) + .Concat(sidebarItems.Where(x => (x as LocationItem)?.ChildItems is not null).SelectMany(x => ((LocationItem)x).ChildItems).Where(x => !string.IsNullOrWhiteSpace(x.Path))) .ToList(); if (string.IsNullOrEmpty(value)) @@ -88,13 +112,13 @@ public void UpdateSidebarSelectedItemFromArgs(string arg) return; } - item = sidebarItems.FirstOrDefault(x => x.Path.Equals(value, StringComparison.OrdinalIgnoreCase)); - item ??= sidebarItems.FirstOrDefault(x => x.Path.Equals(value + "\\", StringComparison.OrdinalIgnoreCase)); - item ??= sidebarItems.Where(x => value.StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)).MaxBy(x => x.Path.Length); - item ??= sidebarItems.FirstOrDefault(x => x.Path.Equals(Path.GetPathRoot(value), StringComparison.OrdinalIgnoreCase)); + item = filteredItems.FirstOrDefault(x => x.Path.Equals(value, StringComparison.OrdinalIgnoreCase)); + item ??= filteredItems.FirstOrDefault(x => x.Path.Equals(value + "\\", StringComparison.OrdinalIgnoreCase)); + item ??= filteredItems.Where(x => value.StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)).MaxBy(x => x.Path.Length); + item ??= filteredItems.FirstOrDefault(x => x.Path.Equals(Path.GetPathRoot(value), StringComparison.OrdinalIgnoreCase)); if (item is null && value == "Home") - item = sidebarItems.FirstOrDefault(x => x.Path.Equals("Home")); + item = filteredItems.FirstOrDefault(x => x.Path.Equals("Home")); if (SidebarSelectedItem != item) SidebarSelectedItem = item; @@ -209,9 +233,10 @@ public INavigationControlItem SidebarSelectedItem public SidebarViewModel() { - dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + fileTagsService = Ioc.Default.GetRequiredService(); - SideBarItems = new BulkConcurrentObservableCollection(); + sidebarItems = new BulkConcurrentObservableCollection(); UserSettingsService.OnSettingChangedEvent += UserSettingsService_OnSettingChangedEvent; CreateItemHomeAsync(); @@ -230,6 +255,18 @@ public SidebarViewModel() networkDrivesViewModel.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Network, args); App.WSLDistroManager.DataChanged += Manager_DataChanged; App.FileTagsManager.DataChanged += Manager_DataChanged; + SidebarDisplayMode = UserSettingsService.AppearanceSettingsService.IsSidebarOpen ? SidebarDisplayMode.Expanded : SidebarDisplayMode.Compact; + + HideSectionCommand = new RelayCommand(HideSection); + UnpinItemCommand = new RelayCommand(UnpinItem); + PinItemCommand = new RelayCommand(PinItem); + OpenInNewTabCommand = new AsyncRelayCommand(OpenInNewTab); + OpenInNewWindowCommand = new AsyncRelayCommand(OpenInNewWindow); + OpenInNewPaneCommand = new AsyncRelayCommand(OpenInNewPane); + EjectDeviceCommand = new AsyncRelayCommand(EjectDevice); + FormatDriveCommand = new RelayCommand(FormatDrive); + OpenPropertiesCommand = new RelayCommand(OpenProperties); + ReorderItemsCommand = new AsyncRelayCommand(ReorderItems); } private Task CreateItemHomeAsync() @@ -363,10 +400,15 @@ await lib.CheckDefaultSaveFolderAccess() && } } - if (IsSidebarOpen) + section.IsExpanded = Ioc.Default.GetRequiredService().Get(section.Text == "SidebarFavorites".GetLocalizedResource(), $"section:{section.Text.Replace('\\', '_')}"); + section.PropertyChanged += Section_PropertyChanged; + } + + private void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (sender is LocationItem section && e.PropertyName == nameof(section.IsExpanded)) { - // Restore expanded state when section has items - section.IsExpanded = Ioc.Default.GetRequiredService().Get(section.Text == "SidebarFavorites".GetLocalizedResource(), $"section:{section.Text.Replace('\\', '_')}"); + Ioc.Default.GetRequiredService().Set(section.IsExpanded, $"section:{section.Text.Replace('\\', '_')}"); } } @@ -378,7 +420,7 @@ private async Task GetOrCreateSection(SectionType sectionType) private LocationItem? GetSection(SectionType sectionType) { - return SideBarItems.FirstOrDefault(x => x.Section == sectionType) as LocationItem; + return sidebarItems.FirstOrDefault(x => x.Section == sectionType) as LocationItem; } private async Task CreateSection(SectionType sectionType) @@ -524,8 +566,8 @@ private LocationItem BuildSection(string sectionName, SectionType sectionType, C private void AddSectionToSideBar(LocationItem section) { - var index = SectionOrder.TakeWhile(x => x != section.Section).Select(x => SideBarItems.Any(item => item.Section == x) ? 1 : 0).Sum(); - SideBarItems.Insert(Math.Min(index, SideBarItems.Count), section); + var index = SectionOrder.TakeWhile(x => x != section.Section).Select(x => sidebarItems.Any(item => item.Section == x) ? 1 : 0).Sum(); + sidebarItems.Insert(Math.Min(index, sidebarItems.Count), section); } public async Task UpdateSectionVisibility(SectionType sectionType, bool show) @@ -552,7 +594,7 @@ public async Task UpdateSectionVisibility(SectionType sectionType, bool show) } else { - SideBarItems.Remove(SideBarItems.FirstOrDefault(x => x.Section == sectionType)); + sidebarItems.Remove(sidebarItems.FirstOrDefault(x => x.Section == sectionType)); } } @@ -610,21 +652,654 @@ public void Dispose() App.FileTagsManager.DataChanged -= Manager_DataChanged; } - public void SidebarControl_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) - { - SidebarDisplayMode = args.DisplayMode; - } - public void UpdateTabControlMargin() { TabControlMargin = SidebarDisplayMode switch { // This prevents the pane toggle button from overlapping the tab control in minimal mode - NavigationViewDisplayMode.Minimal => new GridLength(44, GridUnitType.Pixel), + SidebarDisplayMode.Minimal => new GridLength(44, GridUnitType.Pixel), _ => new GridLength(0, GridUnitType.Pixel), }; } + public async void HandleItemContextInvoked(object sender, ItemContextInvokedArgs args) + + { + if (sender is not FrameworkElement sidebarItem) return; + + if (args.Item is not INavigationControlItem item) + { + // We are in the pane context requested path + PaneFlyout.ShowAt(sender as FrameworkElement, args.Position); + return; + } + + if (item is FileTagItem tagItem) + { + var cts = new CancellationTokenSource(); + var items = new List<(string path, bool isFolder)>(); + + await foreach (var taggedItem in fileTagsService.GetItemsForTagAsync(tagItem.FileTag.Uid, cts.Token)) + { + items.Add(( + taggedItem.Storable.TryGetPath() ?? string.Empty, + taggedItem.Storable is IFolder)); + } + + SelectedTagChanged?.Invoke(this, new SelectedTagChangedEventArgs(items)); + } + + rightClickedItem = item; + var itemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full }; + itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; + + var menuItems = GetLocationItemMenuItems(item, itemContextMenuFlyout); + var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); + + secondaryElements.OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); + + secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); + itemContextMenuFlyout.ShowAt(sidebarItem, new FlyoutShowOptions { Position = args.Position }); + + if (item.MenuOptions.ShowShellItems) + _ = ShellContextmenuHelper.LoadShellMenuItems(rightClickedItem.Path, itemContextMenuFlyout, item.MenuOptions); + } + + public async void HandleItemInvoked(object item) + { + if (item is not INavigationControlItem navigationControlItem) return; + var navigationPath = item as string; + + if (await DriveHelpers.CheckEmptyDrive(navigationPath)) + return; + + var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); + if (ctrlPressed && navigationPath is not null) + { + await NavigationHelpers.OpenPathInNewTab(navigationPath); + return; + } + + // Type of page to navigate + Type? sourcePageType = null; + + switch (navigationControlItem.ItemType) + { + case NavigationControlItemType.Location: + { + // Get the path of the invoked item + var ItemPath = navigationControlItem.Path; + + if (ItemPath is null) + ItemPath = navigationControlItem.Text; + + // Home item + if (ItemPath != null && ItemPath.Equals("Home", StringComparison.OrdinalIgnoreCase)) + { + navigationPath = "Home"; + sourcePageType = typeof(HomePage); + } + else + { + navigationPath = navigationControlItem.Path; + } + break; + } + + case NavigationControlItemType.FileTag: + var tagPath = navigationControlItem.Path; // Get the path of the invoked item + if (PaneHolder?.ActivePane is IShellPage shp) + { + shp.NavigateToPath(tagPath, new NavigationArguments() + { + IsSearchResultPage = true, + SearchPathParam = "Home", + SearchQuery = tagPath, + AssociatedTabInstance = shp, + NavPathParam = tagPath + }); + } + return; + + default: + { + navigationPath = navigationControlItem.Path; + break; + } + } + + if (PaneHolder?.ActivePane is IShellPage shellPage) + shellPage.NavigateToPath(navigationPath, sourcePageType); + } + + public readonly ICommand CreateLibraryCommand = new AsyncRelayCommand(LibraryManager.ShowCreateNewLibraryDialog); + + public readonly ICommand RestoreLibrariesCommand = new AsyncRelayCommand(LibraryManager.ShowRestoreDefaultLibrariesDialog); + + private ICommand HideSectionCommand { get; } + + private ICommand PinItemCommand { get; } + + private ICommand UnpinItemCommand { get; } + + private ICommand OpenInNewTabCommand { get; } + + private ICommand OpenInNewWindowCommand { get; } + + private ICommand OpenInNewPaneCommand { get; } + + private ICommand EjectDeviceCommand { get; } + + private ICommand FormatDriveCommand { get; } + + private ICommand OpenPropertiesCommand { get; } + + private ICommand ReorderItemsCommand { get; } + + private async Task OpenInNewPane() + { + if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) + return; + PaneHolder.OpenPathInNewPane(rightClickedItem.Path); + } + + private async Task OpenInNewTab() + { + if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) + return; + + await NavigationHelpers.OpenPathInNewTab(rightClickedItem.Path); + } + + private async Task OpenInNewWindow() + { + if (await DriveHelpers.CheckEmptyDrive(rightClickedItem.Path)) + return; + + await NavigationHelpers.OpenPathInNewWindowAsync(rightClickedItem.Path); + } + + private void PinItem() + { + if (rightClickedItem is DriveItem) + _ = QuickAccessService.PinToSidebar(new[] { rightClickedItem.Path }); + } + private void UnpinItem() + { + if (rightClickedItem.Section == SectionType.Favorites || rightClickedItem is DriveItem) + _ = QuickAccessService.UnpinFromSidebar(rightClickedItem.Path); + } + + private void HideSection() + { + switch (rightClickedItem.Section) + { + case SectionType.Favorites: + UserSettingsService.GeneralSettingsService.ShowFavoritesSection = false; + break; + case SectionType.Library: + UserSettingsService.GeneralSettingsService.ShowLibrarySection = false; + break; + case SectionType.CloudDrives: + UserSettingsService.GeneralSettingsService.ShowCloudDrivesSection = false; + break; + case SectionType.Drives: + UserSettingsService.GeneralSettingsService.ShowDrivesSection = false; + break; + case SectionType.Network: + UserSettingsService.GeneralSettingsService.ShowNetworkDrivesSection = false; + break; + case SectionType.WSL: + UserSettingsService.GeneralSettingsService.ShowWslSection = false; + break; + case SectionType.FileTag: + UserSettingsService.GeneralSettingsService.ShowFileTagsSection = false; + break; + } + } + + private async Task ReorderItems() + { + var dialog = new ReorderSidebarItemsDialogViewModel(); + var dialogService = Ioc.Default.GetRequiredService(); + var result = await dialogService.ShowDialogAsync(dialog); + } + + private void OpenProperties(CommandBarFlyout menu) + { + EventHandler flyoutClosed = null!; + flyoutClosed = (s, e) => + { + menu.Closed -= flyoutClosed; + if (rightClickedItem is DriveItem) + FilePropertiesHelpers.OpenPropertiesWindow(rightClickedItem, PaneHolder.ActivePane); + else if (rightClickedItem is LibraryLocationItem library) + FilePropertiesHelpers.OpenPropertiesWindow(new LibraryItem(library), PaneHolder.ActivePane); + else if (rightClickedItem is LocationItem locationItem) + { + ListedItem listedItem = new ListedItem(null!) + { + ItemPath = locationItem.Path, + ItemNameRaw = locationItem.Text, + PrimaryItemAttribute = StorageItemTypes.Folder, + ItemType = "Folder".GetLocalizedResource(), + }; + + FilePropertiesHelpers.OpenPropertiesWindow(listedItem, PaneHolder.ActivePane); + } + }; + menu.Closed += flyoutClosed; + } + + private async Task EjectDevice() + { + var result = await DriveHelpers.EjectDeviceAsync(rightClickedItem.Path); + await UIHelpers.ShowDeviceEjectResultAsync(rightClickedItem is DriveItem driveItem ? driveItem.Type : Data.Items.DriveType.Unknown, result); + } + + private void FormatDrive() + { + Win32API.OpenFormatDriveDialog(rightClickedItem.Path); + } + + private List GetLocationItemMenuItems(INavigationControlItem item, CommandBarFlyout menu) + { + var options = item.MenuOptions; + + var favoriteModel = App.QuickAccessManager.Model; + var favoriteIndex = favoriteModel.IndexOfItem(item); + var favoriteCount = favoriteModel.FavoriteItems.Count; + + var isFavoriteItem = item.Section is SectionType.Favorites && favoriteIndex is not -1; + var showMoveItemUp = isFavoriteItem && favoriteIndex > 0; + var showMoveItemDown = isFavoriteItem && favoriteIndex < favoriteCount - 1; + + var isDriveItem = item is DriveItem; + var isDriveItemPinned = isDriveItem && ((DriveItem)item).IsPinned; + + return new List() + { + new ContextMenuFlyoutItemViewModel() + { + Text = "SideBarCreateNewLibrary/Text".GetLocalizedResource(), + Glyph = "\uE710", + Command = CreateLibraryCommand, + ShowItem = options.IsLibrariesHeader + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "SideBarRestoreLibraries/Text".GetLocalizedResource(), + Glyph = "\uE10E", + Command = RestoreLibrariesCommand, + ShowItem = options.IsLibrariesHeader + }, + new ContextMenuFlyoutItemViewModelBuilder(Commands.EmptyRecycleBin) + { + IsVisible = options.ShowEmptyRecycleBin, + }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(Commands.RestoreAllRecycleBin) + { + IsVisible = options.ShowEmptyRecycleBin, + }.Build(), + new ContextMenuFlyoutItemViewModel() + { + Text = "OpenInNewTab".GetLocalizedResource(), + OpacityIcon = new OpacityIconModel() + { + OpacityIconStyle = "ColorIconOpenInNewTab", + }, + Command = OpenInNewTabCommand, + ShowItem = options.IsLocationItem && UserSettingsService.GeneralSettingsService.ShowOpenInNewTab + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "OpenInNewWindow".GetLocalizedResource(), + OpacityIcon = new OpacityIconModel() + { + OpacityIconStyle = "ColorIconOpenInNewWindow", + }, + Command = OpenInNewWindowCommand, + ShowItem = options.IsLocationItem && UserSettingsService.GeneralSettingsService.ShowOpenInNewTab + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "OpenInNewPane".GetLocalizedResource(), + Command = OpenInNewPaneCommand, + ShowItem = options.IsLocationItem && UserSettingsService.GeneralSettingsService.ShowOpenInNewPane + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "PinToFavorites".GetLocalizedResource(), + OpacityIcon = new OpacityIconModel() + { + OpacityIconStyle = "ColorIconPinToFavorites", + }, + Command = PinItemCommand, + ShowItem = isDriveItem && !isDriveItemPinned + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "UnpinFromFavorites".GetLocalizedResource(), + OpacityIcon = new OpacityIconModel() + { + OpacityIconStyle = "ColorIconUnpinFromFavorites", + }, + Command = UnpinItemCommand, + ShowItem = options.ShowUnpinItem || isDriveItemPinned + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "ReorderSidebarItemsDialogText".GetLocalizedResource(), + Glyph = "\uE8D8", + Command = ReorderItemsCommand, + ShowItem = isFavoriteItem || item.Section is SectionType.Favorites + }, + new ContextMenuFlyoutItemViewModel() + { + Text = string.Format("SideBarHideSectionFromSideBar/Text".GetLocalizedResource(), rightClickedItem.Text), + Glyph = "\uE77A", + Command = HideSectionCommand, + ShowItem = options.ShowHideSection + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "SideBarEjectDevice/Text".GetLocalizedResource(), + Command = EjectDeviceCommand, + ShowItem = options.ShowEjectDevice + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "FormatDriveText".GetLocalizedResource(), + Command = FormatDriveCommand, + CommandParameter = item, + ShowItem = options.ShowFormatDrive + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "Properties".GetLocalizedResource(), + OpacityIcon = new OpacityIconModel() + { + OpacityIconStyle = "ColorIconProperties", + }, + Command = OpenPropertiesCommand, + CommandParameter = menu, + ShowItem = options.ShowProperties + }, + new ContextMenuFlyoutItemViewModel() + { + ItemType = ContextMenuFlyoutItemType.Separator, + Tag = "OverflowSeparator", + IsHidden = !options.ShowShellItems, + }, + new ContextMenuFlyoutItemViewModel() + { + Text = "Loading".GetLocalizedResource(), + Glyph = "\xE712", + Items = new List(), + ID = "ItemOverflow", + Tag = "ItemOverflow", + IsEnabled = false, + IsHidden = !options.ShowShellItems, + } + }.Where(x => x.ShowItem).ToList(); + } + + public async void HandleItemDragOver(ItemDragOverEventArgs args) + { + if (args.DropTarget is LocationItem locationItem) + { + HandleLocationItemDragOver(locationItem, args); + } + else if (args.DropTarget is DriveItem driveItem) + { + HandleDriveItemDragOver(driveItem, args); + } + else if (args.DropTarget is FileTagItem fileTagItem) + { + HandleTagItemDragOver(fileTagItem, args); + } + } + + private async void HandleLocationItemDragOver(LocationItem locationItem, ItemDragOverEventArgs args) + { + var rawEvent = args.RawEvent; + var deferral = rawEvent.GetDeferral(); + + if (Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) + { + args.RawEvent.Handled = true; + + var isPathNull = string.IsNullOrEmpty(locationItem.Path); + var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); + var hasStorageItems = storageItems.Any(); + + if (isPathNull && hasStorageItems && SectionType.Favorites.Equals(locationItem.Section)) + { + var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.FavoriteItems.Contains(item.Path)); + + if (!haveFoldersToPin) + { + rawEvent.AcceptedOperation = DataPackageOperation.None; + } + else + { + var captionText = "PinToFavorites".GetLocalizedResource(); + CompleteDragEventArgs(rawEvent, captionText, DataPackageOperation.Move); + } + } + else if (isPathNull || + (hasStorageItems && storageItems.AreItemsAlreadyInFolder(locationItem.Path)) || + locationItem.Path.StartsWith("Home", StringComparison.OrdinalIgnoreCase)) + { + rawEvent.AcceptedOperation = DataPackageOperation.None; + } + else if (hasStorageItems is false) + { + rawEvent.AcceptedOperation = DataPackageOperation.None; + } + else + { + string captionText; + DataPackageOperation operationType; + if (locationItem.Path.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) + { + captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Move; + } + else if (rawEvent.Modifiers.HasFlag(DragDropModifiers.Alt) || rawEvent.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift)) + { + captionText = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Link; + } + else if (rawEvent.Modifiers.HasFlag(DragDropModifiers.Control)) + { + captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Copy; + } + else if (rawEvent.Modifiers.HasFlag(DragDropModifiers.Shift)) + { + captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Move; + } + else if (storageItems.Any(x => x.Item is ZipStorageFile || x.Item is ZipStorageFolder) + || ZipStorageFolder.IsZipPath(locationItem.Path)) + { + captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Copy; + } + else if (locationItem.IsDefaultLocation || storageItems.AreItemsInSameDrive(locationItem.Path)) + { + captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Move; + } + else + { + captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), locationItem.Text); + operationType = DataPackageOperation.Copy; + } + CompleteDragEventArgs(rawEvent, captionText, operationType); + } + } + + deferral.Complete(); + } + + private async void HandleDriveItemDragOver(DriveItem driveItem, ItemDragOverEventArgs args) + { + if (!Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) + return; + + var deferral = args.RawEvent.GetDeferral(); + args.RawEvent.Handled = true; + + var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); + var hasStorageItems = storageItems.Any(); + + if ("Unknown".GetLocalizedResource().Equals(driveItem.SpaceText, StringComparison.OrdinalIgnoreCase) || + (hasStorageItems && storageItems.AreItemsAlreadyInFolder(driveItem.Path))) + { + args.RawEvent.AcceptedOperation = DataPackageOperation.None; + } + else if (!hasStorageItems) + { + args.RawEvent.AcceptedOperation = DataPackageOperation.None; + } + else + { + string captionText; + DataPackageOperation operationType; + if (args.RawEvent.Modifiers.HasFlag(DragDropModifiers.Alt) || args.RawEvent.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift)) + { + captionText = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), driveItem.Text); + operationType = DataPackageOperation.Link; + } + else if (args.RawEvent.Modifiers.HasFlag(DragDropModifiers.Control)) + { + captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), driveItem.Text); + operationType = DataPackageOperation.Copy; + } + else if (args.RawEvent.Modifiers.HasFlag(DragDropModifiers.Shift)) + { + captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), driveItem.Text); + operationType = DataPackageOperation.Move; + } + else if (storageItems.AreItemsInSameDrive(driveItem.Path)) + { + captionText = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), driveItem.Text); + operationType = DataPackageOperation.Move; + } + else + { + captionText = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), driveItem.Text); + operationType = DataPackageOperation.Copy; + } + CompleteDragEventArgs(args.RawEvent, captionText, operationType); + } + + deferral.Complete(); + } + + private async void HandleTagItemDragOver(FileTagItem tagItem, ItemDragOverEventArgs args) + { + if (!Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) + return; + + var deferral = args.RawEvent.GetDeferral(); + args.RawEvent.Handled = true; + + var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); + + if (!storageItems.Any()) + { + args.RawEvent.AcceptedOperation = DataPackageOperation.None; + } + else + { + args.RawEvent.DragUIOverride.IsCaptionVisible = true; + args.RawEvent.DragUIOverride.Caption = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), tagItem.Text); + args.RawEvent.AcceptedOperation = DataPackageOperation.Link; + } + + deferral.Complete(); + } + + + public async void HandleItemDropped(ItemDroppedEventArgs args) + { + if (args.DropTarget is LocationItem locationItem) + { + HandleLocationItemDropped(locationItem, args); + } + else if (args.DropTarget is DriveItem driveItem) + { + HandleDriveItemDropped(driveItem, args); + } + else if (args.DropTarget is FileTagItem fileTagItem) + { + HandleTagItemDropped(fileTagItem, args); + } + } + + private async void HandleLocationItemDropped(LocationItem locationItem, ItemDroppedEventArgs args) + { + if (Utils.Storage.FilesystemHelpers.HasDraggedStorageItems(args.DroppedItem)) + { + var deferral = args.RawEvent.GetDeferral(); + if (string.IsNullOrEmpty(locationItem.Path) && SectionType.Favorites.Equals(locationItem.Section)) // Pin to Favorites section + { + var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); + foreach (var item in storageItems) + { + if (item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.FavoriteItems.Contains(item.Path)) + QuickAccessService.PinToSidebar(item.Path); + } + } + else + { + await FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.DroppedItem, locationItem.Path, false, true); + } + deferral.Complete(); + } + } + + private async void HandleDriveItemDropped(DriveItem driveItem, ItemDroppedEventArgs args) + { + var deferral = args.RawEvent.GetDeferral(); + + await FilesystemHelpers.PerformOperationTypeAsync(args.RawEvent.AcceptedOperation, args.RawEvent.DataView, driveItem.Path, false, true); + + deferral.Complete(); + await Task.Yield(); + } + + private async void HandleTagItemDropped(FileTagItem fileTagItem, ItemDroppedEventArgs args) + { + var deferral = args.RawEvent.GetDeferral(); + + var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); + foreach (var item in storageItems.Where(x => !string.IsNullOrEmpty(x.Path))) + { + var listedItem = new ListedItem(null) + { + ItemPath = item.Path, + FileFRN = await FileTagsHelper.GetFileFRN(item.Item), + FileTags = new[] { fileTagItem.FileTag.Uid } + }; + } + + deferral.Complete(); + await Task.Yield(); + } + + private static DragEventArgs CompleteDragEventArgs(DragEventArgs e, string captionText, DataPackageOperation operationType) + { + e.DragUIOverride.IsCaptionVisible = true; + e.DragUIOverride.Caption = captionText; + e.AcceptedOperation = operationType; + return e; + } + private GridLength tabControlMargin; public GridLength TabControlMargin { diff --git a/src/Files.App/Views/LayoutModes/ColumnViewBrowser.xaml b/src/Files.App/Views/LayoutModes/ColumnViewBrowser.xaml index 22b1d739e270..ff90fa3f9017 100644 --- a/src/Files.App/Views/LayoutModes/ColumnViewBrowser.xaml +++ b/src/Files.App/Views/LayoutModes/ColumnViewBrowser.xaml @@ -23,7 +23,7 @@ - + diff --git a/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml b/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml index 635d57b1ffc6..e98a95bced09 100644 --- a/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml +++ b/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml @@ -243,7 +243,7 @@ Height="40" Margin="0" Padding="16,0,0,0" - BorderBrush="{ThemeResource ControlStrokeColorDefault}" + BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderThickness="0,0,0,1" PointerPressed="Grid_PointerPressed"> diff --git a/src/Files.App/Views/MainPage.xaml b/src/Files.App/Views/MainPage.xaml index 4033825284f2..e79535b18c99 100644 --- a/src/Files.App/Views/MainPage.xaml +++ b/src/Files.App/Views/MainPage.xaml @@ -4,10 +4,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:helpers="using:Files.App.Helpers" xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:icore="using:Microsoft.Xaml.Interactions.Core" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:sidebar="using:Files.App.UserControls.Sidebar" xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls" + xmlns:triggers="using:CommunityToolkit.WinUI.UI.Triggers" xmlns:uc="using:Files.App.UserControls" xmlns:usercontrols="using:Files.App.UserControls.MultitaskingControl" xmlns:viewmodels="using:Files.App.ViewModels" @@ -32,6 +35,19 @@ + + + + + + + + + + @@ -108,82 +124,91 @@ - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + @@ -275,22 +300,22 @@ - - - - - - + - - + + + + + + + - + diff --git a/src/Files.App/Views/MainPage.xaml.cs b/src/Files.App/Views/MainPage.xaml.cs index 3e4bb22d91e0..c066284102b8 100644 --- a/src/Files.App/Views/MainPage.xaml.cs +++ b/src/Files.App/Views/MainPage.xaml.cs @@ -4,6 +4,11 @@ using CommunityToolkit.WinUI.Helpers; using CommunityToolkit.WinUI.UI; using CommunityToolkit.WinUI.UI.Controls; +using Files.App.Data.Items; +using Files.App.Data.Models; +using Files.App.UserControls.MultitaskingControl; +using Files.App.UserControls.Sidebar; +using Files.Core.Extensions; using Files.App.UserControls.MultitaskingControl; using Microsoft.Extensions.Logging; using Microsoft.UI.Dispatching; @@ -13,6 +18,7 @@ using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Navigation; using System.Runtime.CompilerServices; +using Vanara.Extensions.Reflection; using Windows.ApplicationModel; using Windows.Services.Store; using Windows.Storage; @@ -51,6 +57,7 @@ public MainPage() Commands = Ioc.Default.GetRequiredService(); WindowContext = Ioc.Default.GetRequiredService(); SidebarAdaptiveViewModel = Ioc.Default.GetRequiredService(); + SidebarAdaptiveViewModel.PaneFlyout = (MenuFlyout)Resources["SidebarContextMenu"]; ViewModel = Ioc.Default.GetRequiredService(); OngoingTasksViewModel = Ioc.Default.GetRequiredService(); @@ -198,11 +205,6 @@ private void UpdateNavToolbarProperties() protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.OnNavigatedTo(e); - - SidebarControl.SidebarItemInvoked += SidebarControl_SidebarItemInvoked; - SidebarControl.SidebarItemPropertiesInvoked += SidebarControl_SidebarItemPropertiesInvoked; - SidebarControl.SidebarItemDropped += SidebarControl_SidebarItemDropped; - SidebarControl.SidebarItemNewPaneInvoked += SidebarControl_SidebarItemNewPaneInvoked; } protected override async void OnPreviewKeyDown(KeyRoutedEventArgs e) @@ -263,103 +265,6 @@ protected override void OnLostFocus(RoutedEventArgs e) keyReleased = true; } - private async void SidebarControl_SidebarItemDropped(object sender, SidebarItemDroppedEventArgs e) - { - await SidebarAdaptiveViewModel.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.Package, e.ItemPath, false, true); - e.SignalEvent?.Set(); - } - - private void SidebarControl_SidebarItemPropertiesInvoked(object sender, SidebarItemPropertiesInvokedEventArgs e) - { - if (e.InvokedItemDataContext is DriveItem) - FilePropertiesHelpers.OpenPropertiesWindow(e.InvokedItemDataContext, SidebarAdaptiveViewModel.PaneHolder.ActivePane); - else if (e.InvokedItemDataContext is LibraryLocationItem library) - FilePropertiesHelpers.OpenPropertiesWindow(new LibraryItem(library), SidebarAdaptiveViewModel.PaneHolder.ActivePane); - else if (e.InvokedItemDataContext is LocationItem locationItem) - { - ListedItem listedItem = new ListedItem(null!) - { - ItemPath = locationItem.Path, - ItemNameRaw = locationItem.Text, - PrimaryItemAttribute = StorageItemTypes.Folder, - ItemType = "Folder".GetLocalizedResource(), - }; - - FilePropertiesHelpers.OpenPropertiesWindow(listedItem, SidebarAdaptiveViewModel.PaneHolder.ActivePane); - } - } - - private void SidebarControl_SidebarItemNewPaneInvoked(object sender, SidebarItemNewPaneInvokedEventArgs e) - { - if (e.InvokedItemDataContext is INavigationControlItem navItem) - SidebarAdaptiveViewModel.PaneHolder.OpenPathInNewPane(navItem.Path); - } - - private void SidebarControl_SidebarItemInvoked(object sender, SidebarItemInvokedEventArgs e) - { - var invokedItemContainer = e.InvokedItemContainer; - - // Path to navigate - string? navigationPath; - - // Type of page to navigate - Type? sourcePageType = null; - - switch ((invokedItemContainer.DataContext as INavigationControlItem)?.ItemType) - { - case NavigationControlItemType.Location: - { - // Get the path of the invoked item - var ItemPath = (invokedItemContainer.DataContext as INavigationControlItem)?.Path; - - // Section item - if (string.IsNullOrEmpty(ItemPath)) - { - navigationPath = invokedItemContainer.Tag?.ToString(); - } - // Home item - else if (ItemPath.Equals("Home", StringComparison.OrdinalIgnoreCase)) - { - if (ItemPath.Equals(SidebarAdaptiveViewModel.SidebarSelectedItem?.Path, StringComparison.OrdinalIgnoreCase)) - return; // return if already selected - - navigationPath = "Home"; - sourcePageType = typeof(HomePage); - } - // Any other item - else - { - navigationPath = invokedItemContainer.Tag?.ToString(); - } - break; - } - - case NavigationControlItemType.FileTag: - var tagPath = (invokedItemContainer.DataContext as INavigationControlItem)?.Path; // Get the path of the invoked item - if (SidebarAdaptiveViewModel.PaneHolder?.ActivePane is IShellPage shp) - { - shp.NavigateToPath(tagPath, new NavigationArguments() - { - IsSearchResultPage = true, - SearchPathParam = "Home", - SearchQuery = tagPath, - AssociatedTabInstance = shp, - NavPathParam = tagPath - }); - } - return; - - default: - { - navigationPath = invokedItemContainer.Tag?.ToString(); - break; - } - } - - if (SidebarAdaptiveViewModel.PaneHolder?.ActivePane is IShellPage shellPage) - shellPage.NavigateToPath(navigationPath, sourcePageType); - } - private void Page_Loaded(object sender, RoutedEventArgs e) { // Defers the status bar loading until after the page has loaded to improve startup perf @@ -554,5 +459,13 @@ private void PaneSplitter_ManipulationStarted(object sender, ManipulationStarted this.ChangeCursor(InputSystemCursor.Create(PaneSplitter.GripperCursor == GridSplitter.GripperCursorType.SizeWestEast ? InputSystemCursorShape.SizeWestEast : InputSystemCursorShape.SizeNorthSouth)); } + + private void TogglePaneButton_Click(object sender, RoutedEventArgs e) + { + if (SidebarControl.DisplayMode == SidebarDisplayMode.Minimal) + { + SidebarControl.IsPaneOpen = !SidebarControl.IsPaneOpen; + } + } } } diff --git a/src/Files.App/Views/PaneHolderPage.xaml b/src/Files.App/Views/PaneHolderPage.xaml index bef476c41261..4b43ddbbb335 100644 --- a/src/Files.App/Views/PaneHolderPage.xaml +++ b/src/Files.App/Views/PaneHolderPage.xaml @@ -122,7 +122,7 @@ + Value="{ThemeResource DividerStrokeColorDefault}" /> + Value="{ThemeResource DividerStrokeColorDefault}" />