From 4b0b85f39cec8d9b04302f41383d4f64b8e6fca1 Mon Sep 17 00:00:00 2001 From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com> Date: Mon, 6 Jan 2025 02:33:33 +0100 Subject: [PATCH 1/8] Added design and basic functionality for Shelf --- src/Files.App/Data/Items/ShelfItem.cs | 32 +++++++ .../UserControls/Pane/ShelfPane.xaml | 62 ++++++++++++- .../UserControls/Pane/ShelfPane.xaml.cs | 87 +++++++++++++++++++ src/Files.App/Views/MainPage.xaml | 1 + src/Files.Shared/Utils/IAsyncInitialize.cs | 5 +- src/Files.Shared/Utils/IWrapper.cs | 14 +++ 6 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 src/Files.App/Data/Items/ShelfItem.cs create mode 100644 src/Files.Shared/Utils/IWrapper.cs diff --git a/src/Files.App/Data/Items/ShelfItem.cs b/src/Files.App/Data/Items/ShelfItem.cs new file mode 100644 index 000000000000..bf8d5b461db2 --- /dev/null +++ b/src/Files.App/Data/Items/ShelfItem.cs @@ -0,0 +1,32 @@ +using Files.Shared.Utils; + +namespace Files.App.Data.Items +{ + [Bindable(true)] + public sealed partial class ShelfItem : ObservableObject, IWrapper, IAsyncInitialize + { + private readonly IImageService _imageService; + + [ObservableProperty] private IImage? _Icon; + [ObservableProperty] private string? _Name; + [ObservableProperty] private string? _Path; + + /// + public IStorable Inner { get; } + + public ShelfItem(IStorable storable, IImage? icon = null) + { + _imageService = Ioc.Default.GetRequiredService(); + Inner = storable; + Icon = icon; + Name = storable.Name; + Path = storable.Id; + } + + /// + public async Task InitAsync(CancellationToken cancellationToken = default) + { + Icon = await _imageService.GetIconAsync(Inner, cancellationToken); + } + } +} diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 1804873e84d6..4e690fd3b30c 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -3,20 +3,74 @@ x:Class="Files.App.UserControls.ShelfPane" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:Files.App.Controls" xmlns:converters="using:Files.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:data="using:Files.App.Data.Items" - xmlns:helpers="using:Files.App.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:usercontrols="using:Files.App.UserControls" mc:Ignorable="d"> + + + + + CornerRadius="8" + DragOver="Shelf_DragOver" + Drop="Shelf_Drop" + RowSpacing="8"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs index 25946f599056..bbc2cac1fbb3 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs @@ -1,7 +1,13 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Input; +using Vanara.PInvoke; +using Windows.ApplicationModel.DataTransfer; +using WinRT; namespace Files.App.UserControls { @@ -9,7 +15,88 @@ public sealed partial class ShelfPane : UserControl { public ShelfPane() { + // TODO: [Shelf] Remove once view model is connected + ItemsSource = new ObservableCollection(); + InitializeComponent(); } + + private void Shelf_DragOver(object sender, DragEventArgs e) + { + if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView)) + return; + + e.Handled = true; + e.AcceptedOperation = DataPackageOperation.Link; + } + + private async void Shelf_Drop(object sender, DragEventArgs e) + { + if (ItemsSource is null) + return; + + // Get items + var storageService = Ioc.Default.GetRequiredService(); + var storageItems = (await FilesystemHelpers.GetDraggedStorageItems(e.DataView)).ToArray(); + + // Add to list + foreach (var item in storageItems) + { + var storable = item switch + { + StorageFileWithPath => (IStorable?)await storageService.TryGetFileAsync(item.Path), + StorageFolderWithPath => (IStorable?)await storageService.TryGetFolderAsync(item.Path), + _ => null + }; + + if (storable is null) + continue; + + var shelfItem = new ShelfItem(storable); + _ = shelfItem.InitAsync(); + + ItemsSource.Add(shelfItem); + } + } + + private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + { + if (ItemsSource is null) + return; + + var shellItemList = SafetyExtensions.IgnoreExceptions(() => ItemsSource.Select(x => new Vanara.Windows.Shell.ShellItem(x.Inner.Id)).ToArray()); + if (shellItemList?[0].FileSystemPath is not null) + { + var iddo = shellItemList[0].Parent?.GetChildrenUIObjects(HWND.NULL, shellItemList); + if (iddo is null) + return; + + shellItemList.ForEach(x => x.Dispose()); + var dataObjectProvider = e.Data.As(); + dataObjectProvider.SetDataObject(iddo); + } + else + { + // Only support IStorageItem capable paths + var storageItems = ItemsSource.Select(x => VirtualStorageItem.FromPath(x.Inner.Id)); + e.Data.SetStorageItems(storageItems, false); + } + } + + public IList? ItemsSource + { + get => (IList?)GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(IList), typeof(ShelfPane), new PropertyMetadata(null)); + + public ICommand? ClearCommand + { + get => (ICommand?)GetValue(ClearCommandProperty); + set => SetValue(ClearCommandProperty, value); + } + public static readonly DependencyProperty ClearCommandProperty = + DependencyProperty.Register(nameof(ClearCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null)); } } diff --git a/src/Files.App/Views/MainPage.xaml b/src/Files.App/Views/MainPage.xaml index 0c9da874c568..74aec3c6d77c 100644 --- a/src/Files.App/Views/MainPage.xaml +++ b/src/Files.App/Views/MainPage.xaml @@ -248,6 +248,7 @@ ShowInfoText="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}" Visibility="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}" /> + + /// Wraps and exposes implementation for access. + /// + /// The wrapped type. + public interface IWrapper + { + /// + /// Gets the inner member wrapped by the implementation. + /// + T Inner { get; } + } +} From 5cd2700390cde06d89a492776f953cc6d54b846a Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Sun, 5 Jan 2025 20:51:10 -0500 Subject: [PATCH 2/8] Localize --- src/Files.App/Strings/en-US/Resources.resw | 7 +++++++ src/Files.App/UserControls/Pane/ShelfPane.xaml | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 02dbe67ff39d..67a86a1e92c3 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4022,4 +4022,11 @@ Show Shelf Pane + + Shelf + 'Shelf' refers to the Shelf Pane feature, where users can conveniently drag and drop files for quick access and perform bulk actions with ease. + + + Clear items + \ No newline at end of file diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 4e690fd3b30c..da98eec6cd27 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -6,6 +6,7 @@ xmlns:converters="using:Files.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:data="using:Files.App.Data.Items" + xmlns:helpers="using:Files.App.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> @@ -41,7 +42,7 @@ + Text="{helpers:ResourceString Name=Shelf}" /> @@ -70,7 +71,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Command="{x:Bind ClearCommand, Mode=OneWay}" - Content="Clear Items" /> + Content="{helpers:ResourceString Name=ClearItems}" /> From a39e0f408f0c28ea15948c2efe259617567f0988 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:05:47 -0500 Subject: [PATCH 3/8] Improved xaml --- .../UserControls/Pane/ShelfPane.xaml | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index da98eec6cd27..70494ae6e56b 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -16,7 +16,6 @@ - - + - - + Spacing="8"> + - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + Grid.Row="1" + Padding="12,8,12,8" + Spacing="4"> + + + + Date: Mon, 6 Jan 2025 03:07:39 +0100 Subject: [PATCH 4/8] Fix icon transparency issues (Shelf and Tags) --- src/Files.App/Data/Contracts/ImagingService.cs | 7 ++----- src/Files.App/Data/Items/ShelfItem.cs | 10 +++++++++- src/Files.App/UserControls/Pane/ShelfPane.xaml | 12 ++++++++++++ src/Files.App/UserControls/Pane/ShelfPane.xaml.cs | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Files.App/Data/Contracts/ImagingService.cs b/src/Files.App/Data/Contracts/ImagingService.cs index 390824ac9d93..4c793aae915d 100644 --- a/src/Files.App/Data/Contracts/ImagingService.cs +++ b/src/Files.App/Data/Contracts/ImagingService.cs @@ -13,15 +13,12 @@ internal sealed class ImagingService : IImageService /// public async Task GetIconAsync(IStorable storable, CancellationToken cancellationToken) { - if (storable is not ILocatableStorable locatableStorable) - return null; - - var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(locatableStorable.Path, 24u, ThumbnailMode.ListView, ThumbnailOptions.ResizeThumbnail); + var iconData = await FileThumbnailHelper.GetIconAsync(storable.Id, 24u, storable is IFolder, IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); if (iconData is null) return null; var bitmapImage = await iconData.ToBitmapAsync(); - return new BitmapImageModel(bitmapImage); + return bitmapImage is null ? null : new BitmapImageModel(bitmapImage); } public async Task GetImageModelFromDataAsync(byte[] rawData) diff --git a/src/Files.App/Data/Items/ShelfItem.cs b/src/Files.App/Data/Items/ShelfItem.cs index bf8d5b461db2..7facfabce5b9 100644 --- a/src/Files.App/Data/Items/ShelfItem.cs +++ b/src/Files.App/Data/Items/ShelfItem.cs @@ -6,6 +6,7 @@ namespace Files.App.Data.Items public sealed partial class ShelfItem : ObservableObject, IWrapper, IAsyncInitialize { private readonly IImageService _imageService; + private readonly ICollection _sourceCollection; [ObservableProperty] private IImage? _Icon; [ObservableProperty] private string? _Name; @@ -14,9 +15,10 @@ public sealed partial class ShelfItem : ObservableObject, IWrapper, I /// public IStorable Inner { get; } - public ShelfItem(IStorable storable, IImage? icon = null) + public ShelfItem(IStorable storable, ICollection sourceCollection, IImage? icon = null) { _imageService = Ioc.Default.GetRequiredService(); + _sourceCollection = sourceCollection; Inner = storable; Icon = icon; Name = storable.Name; @@ -28,5 +30,11 @@ public async Task InitAsync(CancellationToken cancellationToken = default) { Icon = await _imageService.GetIconAsync(Inner, cancellationToken); } + + [RelayCommand] + private void Remove() + { + _sourceCollection.Remove(this); + } } } diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 70494ae6e56b..77c8e93dfb48 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -35,6 +35,7 @@ Grid.Row="0" Padding="12" Spacing="8"> + + + + + + + + + + + @@ -66,6 +77,7 @@ Grid.Row="1" Padding="12,8,12,8" Spacing="4"> + diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs index bbc2cac1fbb3..8739d8682ef3 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs @@ -52,7 +52,7 @@ private async void Shelf_Drop(object sender, DragEventArgs e) if (storable is null) continue; - var shelfItem = new ShelfItem(storable); + var shelfItem = new ShelfItem(storable, ItemsSource); _ = shelfItem.InitAsync(); ItemsSource.Add(shelfItem); From 2e5a997fed9e3ab93afcb4b93127d0ff63627771 Mon Sep 17 00:00:00 2001 From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:09:18 +0100 Subject: [PATCH 5/8] Localize string --- src/Files.App/UserControls/Pane/ShelfPane.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 77c8e93dfb48..0c2dfc6887a6 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -59,7 +59,7 @@ - + From 6bdb7418e6d5e2a5c2af4b6f60bb347471cd5c17 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:35:50 -0500 Subject: [PATCH 6/8] Improved xaml --- src/Files.App/Strings/en-US/Resources.resw | 3 + .../UserControls/Pane/ShelfPane.xaml | 76 ++++++++++--------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 67a86a1e92c3..af73b67e0810 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4029,4 +4029,7 @@ Clear items + + Remove from shelf + \ No newline at end of file diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 0c2dfc6887a6..fd0b21bb94ed 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -16,6 +16,7 @@ + - + - - - - - - - - - - - - - - - - - - - - - - - + + DragItemsStarting="ListView_DragItemsStarting" + ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" + ScrollViewer.VerticalScrollBarVisibility="Auto" + ScrollViewer.VerticalScrollMode="Auto" + SelectionMode="Extended"> + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -87,6 +94,7 @@ VerticalAlignment="Center" Command="{x:Bind ClearCommand, Mode=OneWay}" Content="{helpers:ResourceString Name=ClearItems}" /> + From 8eab22230f5f3ff837de03fb75de6f562ebad53e Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:45:26 -0500 Subject: [PATCH 7/8] Fix icon size --- src/Files.App/Data/Contracts/ImagingService.cs | 2 +- src/Files.App/UserControls/Pane/ShelfPane.xaml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Data/Contracts/ImagingService.cs b/src/Files.App/Data/Contracts/ImagingService.cs index 4c793aae915d..30d3dcd0a427 100644 --- a/src/Files.App/Data/Contracts/ImagingService.cs +++ b/src/Files.App/Data/Contracts/ImagingService.cs @@ -13,7 +13,7 @@ internal sealed class ImagingService : IImageService /// public async Task GetIconAsync(IStorable storable, CancellationToken cancellationToken) { - var iconData = await FileThumbnailHelper.GetIconAsync(storable.Id, 24u, storable is IFolder, IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); + var iconData = await FileThumbnailHelper.GetIconAsync(storable.Id, Constants.ShellIconSizes.Small, storable is IFolder, IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); if (iconData is null) return null; diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index fd0b21bb94ed..952b0ca4861b 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -58,7 +58,10 @@ - + From f57a6498066a8521b6232376c4012cd575fb01f9 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:47:22 -0500 Subject: [PATCH 8/8] Added caption when drag & dropping items --- src/Files.App/Strings/en-US/Resources.resw | 4 ++++ src/Files.App/UserControls/Pane/ShelfPane.xaml.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index af73b67e0810..a219e91f76cc 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4032,4 +4032,8 @@ Remove from shelf + + Add to Shelf + Tooltip that displays when dragging items to the Shelf Pane + \ No newline at end of file diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs index 8739d8682ef3..baf685530c70 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs @@ -27,6 +27,7 @@ private void Shelf_DragOver(object sender, DragEventArgs e) return; e.Handled = true; + e.DragUIOverride.Caption = Strings.AddToShelf.GetLocalizedResource(); e.AcceptedOperation = DataPackageOperation.Link; }