Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace FluidExplorer.Models;
|
||||
|
||||
/// <summary>轻量文件系统条目:由快速枚举一次性填充(不产生额外系统调用)。</summary>
|
||||
public sealed class FileEntry
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string FullPath { get; init; }
|
||||
public bool IsDirectory { get; init; }
|
||||
public long Size { get; init; }
|
||||
public DateTime ModifiedUtc { get; init; }
|
||||
public DateTime CreatedUtc { get; init; }
|
||||
public DateTime AccessedUtc { get; init; }
|
||||
public FileAttributes Attributes { get; init; }
|
||||
|
||||
public string Extension
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDirectory) return string.Empty;
|
||||
var i = Name.LastIndexOf('.');
|
||||
return i > 0 && i < Name.Length - 1 ? Name[(i + 1)..] : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string TypeKey => IsDirectory ? "folder" : (Extension.Length == 0 ? "file" : Extension.ToLowerInvariant());
|
||||
|
||||
public bool IsHidden => (Attributes & FileAttributes.Hidden) != 0;
|
||||
public bool IsSystem => (Attributes & FileAttributes.System) != 0;
|
||||
public bool IsReparsePoint => (Attributes & FileAttributes.ReparsePoint) != 0;
|
||||
public bool IsReadOnly => (Attributes & FileAttributes.ReadOnly) != 0;
|
||||
public bool IsOffline => (Attributes & FileAttributes.Offline) != 0;
|
||||
|
||||
public DateTime ModifiedLocal => ModifiedUtc.ToLocalTime();
|
||||
public DateTime CreatedLocal => CreatedUtc.ToLocalTime();
|
||||
|
||||
/// <summary>Windows 资源管理器风格的尺寸文本(已按 1024 进制并为文件夹留空)。</summary>
|
||||
public string SizeText => IsDirectory ? string.Empty : FormatSize(Size);
|
||||
|
||||
/// <summary>类型描述:优先使用注册表里的友好类型名(惰性、带缓存)。</summary>
|
||||
public string TypeText => IsDirectory ? "文件夹" : Services.Shell.TypeNameResolver.GetTypeName(Extension, IsDirectory);
|
||||
|
||||
public static string FormatSize(long bytes)
|
||||
{
|
||||
if (bytes < 0) return string.Empty;
|
||||
if (bytes < 1024) return $"{bytes} 字节";
|
||||
string[] units = ["KB", "MB", "GB", "TB", "PB"];
|
||||
double v = bytes;
|
||||
int u = -1;
|
||||
do { v /= 1024.0; u++; } while (v >= 1024 && u < units.Length - 1);
|
||||
return v >= 100 ? $"{v:0} {units[u]}" : v >= 10 ? $"{v:0.0} {units[u]}" : $"{v:0.00} {units[u]}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>列表里的一行:可观察包装,缩略图/图标异步补齐,不阻塞滚动。</summary>
|
||||
public sealed class ExplorerItem : INotifyPropertyChanged
|
||||
{
|
||||
private ImageSource? _icon;
|
||||
private bool _isSelected;
|
||||
private bool _isRenaming;
|
||||
private string _renameText = string.Empty;
|
||||
private string? _displayName;
|
||||
private int _iconSize = 16;
|
||||
|
||||
public ExplorerItem(FileEntry entry) => Entry = entry;
|
||||
|
||||
public FileEntry Entry { get; }
|
||||
public string Name => Entry.Name;
|
||||
|
||||
/// <summary>界面显示名("显示文件扩展名"关闭时隐藏扩展名,与资源管理器一致)。</summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get => _displayName ?? Entry.Name;
|
||||
set { if (_displayName != value) { _displayName = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
/// <summary>是否为新建/重命名中的占位项(这些项要滚动到可见并进入编辑状态)。</summary>
|
||||
public bool IsPending { get; set; }
|
||||
public string FullPath => Entry.FullPath;
|
||||
public bool IsDirectory => Entry.IsDirectory;
|
||||
public long Size => Entry.Size;
|
||||
public string SizeText => Entry.SizeText;
|
||||
public string TypeText => Entry.TypeText;
|
||||
public DateTime ModifiedLocal => Entry.ModifiedLocal;
|
||||
public string ModifiedText => Entry.ModifiedLocal.ToString("yyyy/MM/dd HH:mm");
|
||||
public string CreatedText => Entry.CreatedLocal.ToString("yyyy/MM/dd HH:mm");
|
||||
|
||||
/// <summary>图标或缩略图(ImageSource 由 UI 线程创建后写入;先给几何图标占位,避免跳动)。</summary>
|
||||
public ImageSource? Icon
|
||||
{
|
||||
get => _icon;
|
||||
set { if (!ReferenceEquals(_icon, value)) { _icon = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
/// <summary>是否已经替换为真实缩略图(用于淡入动画)。</summary>
|
||||
public bool HasThumbnail { get; set; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (_isSelected == value) return;
|
||||
_isSelected = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ShowCheckBox));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>复选框是否可见(对齐资源管理器:选中时显示,或用户开启了"始终显示项目复选框")。</summary>
|
||||
public bool ShowCheckBox => _isSelected || AlwaysShowCheckBoxes;
|
||||
|
||||
/// <summary>全局设置:始终显示项目复选框。</summary>
|
||||
public static bool AlwaysShowCheckBoxes { get; set; }
|
||||
|
||||
/// <summary>当前视图所需的图标像素尺寸(由窗格在切换视图方式时写入)。</summary>
|
||||
public int IconSize
|
||||
{
|
||||
get => _iconSize;
|
||||
set { if (_iconSize != value) { _iconSize = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
public bool IsRenaming
|
||||
{
|
||||
get => _isRenaming;
|
||||
set { if (_isRenaming != value) { _isRenaming = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
public string RenameText
|
||||
{
|
||||
get => _renameText;
|
||||
set { if (_renameText != value) { _renameText = value; OnPropertyChanged(); } }
|
||||
}
|
||||
|
||||
public string Extension => Entry.Extension;
|
||||
public bool IsHidden => Entry.IsHidden;
|
||||
public bool IsReparsePoint => Entry.IsReparsePoint;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? name = null)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
/// <summary>当前文件夹的内容集合。</summary>
|
||||
public sealed class FolderListing
|
||||
{
|
||||
public required string Path { get; set; }
|
||||
public ObservableCollection<ExplorerItem> Items { get; } = [];
|
||||
public bool IsLoading { get; set; }
|
||||
public bool IsComplete { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public int TotalCount => Items.Count;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace FluidExplorer.Models;
|
||||
|
||||
public enum SortColumn
|
||||
{
|
||||
Name,
|
||||
DateModified,
|
||||
DateCreated,
|
||||
Type,
|
||||
Size,
|
||||
Path,
|
||||
Extension,
|
||||
Attributes
|
||||
}
|
||||
|
||||
public enum SortDirection
|
||||
{
|
||||
Ascending,
|
||||
Descending
|
||||
}
|
||||
|
||||
public sealed record SortSpec(SortColumn Column, SortDirection Direction)
|
||||
{
|
||||
public static SortSpec Default { get; } = new(SortColumn.Name, SortDirection.Ascending);
|
||||
public SortSpec Toggle(SortColumn column)
|
||||
=> Column == column
|
||||
? this with { Direction = Direction == SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending }
|
||||
: new SortSpec(column, SortDirection.Ascending);
|
||||
}
|
||||
|
||||
/// <summary>视图模式:对齐资源管理器(详情/列表/网格三档/内容)。</summary>
|
||||
public enum ViewMode
|
||||
{
|
||||
ExtraLargeIcons,
|
||||
LargeIcons,
|
||||
MediumIcons,
|
||||
SmallIcons,
|
||||
List,
|
||||
Details,
|
||||
Tiles,
|
||||
Content
|
||||
}
|
||||
|
||||
public enum GroupBy
|
||||
{
|
||||
None,
|
||||
Name,
|
||||
DateModified,
|
||||
Type,
|
||||
Size
|
||||
}
|
||||
Reference in New Issue
Block a user