diff --git a/src/GuiApp/FileTime.Avalonia/App.axaml b/src/GuiApp/FileTime.Avalonia/App.axaml index a3122c3..4fce1de 100644 --- a/src/GuiApp/FileTime.Avalonia/App.axaml +++ b/src/GuiApp/FileTime.Avalonia/App.axaml @@ -122,6 +122,7 @@ + diff --git a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs index ba06d62..ab76098 100644 --- a/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs +++ b/src/GuiApp/FileTime.Avalonia/Application/TabContainer.cs @@ -50,7 +50,7 @@ namespace FileTime.Avalonia.Application _selectedItem = value; OnPropertyChanged("SelectedItem"); - SelectedItemChanged(); + SelectedItemChanged().Wait(); } } } @@ -152,7 +152,7 @@ namespace FileTime.Avalonia.Application } var items = await _currentLocation.GetItems(); - if (items != null && items.Count > 0) + if (items?.Count > 0) { foreach (var item in items) { @@ -206,7 +206,7 @@ namespace FileTime.Avalonia.Application } } - private async void SelectedItemChanged() + private async Task SelectedItemChanged() { try { @@ -215,6 +215,15 @@ namespace FileTime.Avalonia.Application catch { } } + public async Task SetCurrentSelectedItem(IItem newItem) + { + try + { + await Tab.SetCurrentSelectedItem(newItem); + } + catch { } + } + public async Task Open() { if (ChildContainer != null) diff --git a/src/GuiApp/FileTime.Avalonia/Assets/material/desktop.svg b/src/GuiApp/FileTime.Avalonia/Assets/material/desktop.svg new file mode 100644 index 0000000..5467d85 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Assets/material/desktop.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Assets/material/folder-music.svg b/src/GuiApp/FileTime.Avalonia/Assets/material/folder-music.svg new file mode 100644 index 0000000..8f4e952 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Assets/material/folder-music.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Converters/ExceptionToStringConverter.cs b/src/GuiApp/FileTime.Avalonia/Converters/ExceptionToStringConverter.cs new file mode 100644 index 0000000..b1aeda9 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/Converters/ExceptionToStringConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace FileTime.Avalonia.Converters +{ + public class ExceptionToStringConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not Exception e) return value; + + if (e is UnauthorizedAccessException) return e.Message; + + return $"{e.Message} ({e.GetType().FullName})"; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/Converters/ItemToImageConverter.cs b/src/GuiApp/FileTime.Avalonia/Converters/ItemToImageConverter.cs index 9b9301a..21288f9 100644 --- a/src/GuiApp/FileTime.Avalonia/Converters/ItemToImageConverter.cs +++ b/src/GuiApp/FileTime.Avalonia/Converters/ItemToImageConverter.cs @@ -31,18 +31,25 @@ namespace FileTime.Avalonia.Converters }; SvgSource? source; - var path = _iconProvider.GetImage(item)!; - if (path.Type == Models.ImagePathType.Absolute) + try { - source = SvgSource.Load(path.Path!, null); + var path = _iconProvider.GetImage(item)!; + if (path.Type == Models.ImagePathType.Absolute) + { + source = SvgSource.Load(path.Path!, null); + } + else if (path.Type == Models.ImagePathType.Raw) + { + return path.Image; + } + else + { + source = SvgSource.Load("avares://FileTime.Avalonia" + path.Path, null); + } } - else if(path.Type == Models.ImagePathType.Raw) + catch { - return path.Image; - } - else - { - source = SvgSource.Load("avares://FileTime.Avalonia" + path.Path, null); + source = SvgSource.Load("avares://FileTime.Avalonia/Assets/material/file.svg", null); } return new SvgImage { Source = source }; } diff --git a/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs b/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs index 6c2fdab..ef88a6a 100644 --- a/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs +++ b/src/GuiApp/FileTime.Avalonia/IconProviders/MaterialIconProvider.cs @@ -1,30 +1,50 @@ -using Avalonia.Media.Imaging; -using FileTime.Avalonia.Misc; using FileTime.Avalonia.Models; using FileTime.Core.Models; using FileTime.Providers.Local; -using System; -using System.IO; +using Syroot.Windows.IO; +using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; namespace FileTime.Avalonia.IconProviders { public class MaterialIconProvider : IIconProvider { + private readonly List _specialPaths = new(); public bool EnableAdvancedIcons { get; set; } = true; + public MaterialIconProvider() + { + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Desktop.Path, GetAssetPath("desktop.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Documents.Path, GetAssetPath("folder-resource.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.DownloadsLocalized.Path, GetAssetPath("folder-download.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.MusicLocalized.Path, GetAssetPath("folder-music.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Pictures.Path, GetAssetPath("folder-images.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Profile.Path, GetAssetPath("folder-home.svg"))); + _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Videos.Path, GetAssetPath("folder-video.svg"))); + } + public ImagePath GetImage(IItem item) { var icon = item is IContainer ? "folder.svg" : "file.svg"; + string? localPath = item switch + { + LocalFolder folder => folder.Directory.FullName, + LocalFile file => file.File.FullName, + _ => null + }; if (EnableAdvancedIcons) { + if (localPath != null && _specialPaths.Find(p => p.Path == localPath) is SpecialPathWithIcon specialPath) + { + return specialPath.IconPath; + } + if (item is IElement element) { - if (element is LocalFile localFile && (element.FullName?.EndsWith(".svg") ?? false)) + if (element is LocalFile && (localPath?.EndsWith(".svg") ?? false)) { - return new ImagePath(ImagePathType.Absolute, localFile.File.FullName); + return new ImagePath(ImagePathType.Absolute, localPath); } icon = !element.Name.Contains('.') ? icon @@ -34,42 +54,13 @@ namespace FileTime.Avalonia.IconProviders _ => icon }; } - /*else if (item is LocalFolder folder && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var file = new FileInfo(Path.Combine(folder.FullName, "desktop.ini")); - if (file.Exists) - { - var lines = File.ReadAllLines(file.FullName); - if (Array.Find(lines, l => l.StartsWith("iconresource", StringComparison.OrdinalIgnoreCase)) is string iconLine) - { - var nameLineValue = string.Join('=', iconLine.Split('=')[1..]); - var environemntVariables = Environment.GetEnvironmentVariables(); - foreach (var keyo in environemntVariables.Keys) - { - if (keyo is string key && environemntVariables[key] is string value) - { - nameLineValue = nameLineValue.Replace($"%{key}%", value); - } - } - - var parts = nameLineValue.Split(','); - if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId)) - { - if (parsedResourceId < 0) parsedResourceId *= -1; - - var extractedIcon = NativeMethodHelpers.GetIconResource(string.Join(',', parts[..^1]), (uint)parsedResourceId); - - var extractedIconAsStream = new MemoryStream(); - extractedIcon.Save(extractedIconAsStream); - extractedIconAsStream.Position = 0; - - return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream)); - } - } - } - }*/ } - return new ImagePath(ImagePathType.Asset, "/Assets/material/" + icon); + return GetAssetPath(icon); + } + + private static ImagePath GetAssetPath(string iconName) + { + return new ImagePath(ImagePathType.Asset, "/Assets/material/" + iconName); } } } diff --git a/src/GuiApp/FileTime.Avalonia/IconProviders/SpecialPathWithIcon.cs b/src/GuiApp/FileTime.Avalonia/IconProviders/SpecialPathWithIcon.cs new file mode 100644 index 0000000..4f9702f --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/IconProviders/SpecialPathWithIcon.cs @@ -0,0 +1,16 @@ +using FileTime.Avalonia.Models; + +namespace FileTime.Avalonia.IconProviders +{ + public class SpecialPathWithIcon + { + public string Path { get; } + public ImagePath IconPath { get; } + + public SpecialPathWithIcon(string path, ImagePath iconPath) + { + Path = path; + IconPath = iconPath; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/IconProviders/WindowsSystemIconHelper.cs b/src/GuiApp/FileTime.Avalonia/IconProviders/WindowsSystemIconHelper.cs new file mode 100644 index 0000000..aaafb11 --- /dev/null +++ b/src/GuiApp/FileTime.Avalonia/IconProviders/WindowsSystemIconHelper.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using Avalonia.Media.Imaging; +using FileTime.Avalonia.Misc; +using FileTime.Avalonia.Models; +using FileTime.Providers.Local; + +namespace FileTime.Avalonia.IconProviders +{ + public static class WindowsSystemIconHelper + { + public static ImagePath? GetImageByDesktopIni(LocalFolder folder) + { + var file = new FileInfo(Path.Combine(folder.FullName, "desktop.ini")); + if (file.Exists) + { + var lines = File.ReadAllLines(file.FullName); + if (Array.Find(lines, l => l.StartsWith("iconresource", StringComparison.OrdinalIgnoreCase)) is string iconLine) + { + var nameLineValue = string.Join('=', iconLine.Split('=')[1..]); + var environemntVariables = Environment.GetEnvironmentVariables(); + foreach (var keyo in environemntVariables.Keys) + { + if (keyo is string key && environemntVariables[key] is string value) + { + nameLineValue = nameLineValue.Replace($"%{key}%", value); + } + } + + var parts = nameLineValue.Split(','); + if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId)) + { + if (parsedResourceId < 0) parsedResourceId *= -1; + + var extractedIcon = NativeMethodHelpers.GetIconResource(string.Join(',', parts[..^1]), (uint)parsedResourceId); + + var extractedIconAsStream = new MemoryStream(); + extractedIcon.Save(extractedIconAsStream); + extractedIconAsStream.Position = 0; + + return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream)); + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index ace11db..b00c744 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -780,7 +780,12 @@ namespace FileTime.Avalonia.ViewModels await AppState.SelectedTab.OpenContainer(newLocation); var selectedItemName = AppState.SelectedTab.SelectedItem?.Item.Name; - if (!(await AppState.SelectedTab.CurrentLocation.GetItems()).Select(i => i.Item.Name).Any(n => n == selectedItemName)) + var currentLocationItems = await AppState.SelectedTab.CurrentLocation.GetItems(); + if(currentLocationItems.FirstOrDefault(i => i.Item.Name.ToLower() == AppState.RapidTravelText.ToLower()) is IItemViewModel matchItem) + { + await AppState.SelectedTab.SetCurrentSelectedItem(matchItem.Item); + } + else if (!currentLocationItems.Select(i => i.Item.Name).Any(n => n == selectedItemName)) { await AppState.SelectedTab.MoveCursorToFirst(); } diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml index 4091fb0..bf4ae59 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml @@ -260,7 +260,7 @@ VerticalAlignment="Stretch" Fill="{DynamicResource ContentSeparatorBrush}" /> - + + IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"> Empty - - - - - - - + + + + + + + + + + + +