Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace FluidExplorer.Helpers;
|
||||
|
||||
/// <summary>bool → Visibility(ConverterParameter="invert" 可反转)。</summary>
|
||||
public sealed class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var flag = value is bool b && b;
|
||||
if (parameter is string s && s.Equals("invert", StringComparison.OrdinalIgnoreCase)) flag = !flag;
|
||||
return flag ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> value is Visibility v && v == Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>字符串非空 → Visible。</summary>
|
||||
public sealed class StringToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
=> string.IsNullOrWhiteSpace(value as string) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>数量 > 0 → Visible。</summary>
|
||||
public sealed class CountToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
=> value is int count && count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>bool 取反。</summary>
|
||||
public sealed class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
=> value is bool b && !b;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> value is bool b && !b;
|
||||
}
|
||||
|
||||
/// <summary>回收站/图库等视图才显示的操作按钮。</summary>
|
||||
public sealed class KindToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var current = value?.ToString();
|
||||
var expected = parameter as string;
|
||||
return string.Equals(current, expected, StringComparison.OrdinalIgnoreCase) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>作业状态 → 中文文案(状态栏/队列面板用)。</summary>
|
||||
public sealed class JobStateTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
=> value is Services.Operations.JobState state
|
||||
? state switch
|
||||
{
|
||||
Services.Operations.JobState.Queued => "排队中",
|
||||
Services.Operations.JobState.Running => "进行中",
|
||||
Services.Operations.JobState.Paused => "已暂停",
|
||||
Services.Operations.JobState.Completed => "已完成",
|
||||
Services.Operations.JobState.CompletedWithErrors => "已完成(部分出错)",
|
||||
Services.Operations.JobState.Cancelled => "已取消",
|
||||
Services.Operations.JobState.Failed => "失败",
|
||||
_ => state.ToString()
|
||||
}
|
||||
: string.Empty;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>作业 → 进度明细文案:条目数 / 体积 / 速度 / 剩余时间。</summary>
|
||||
public sealed class JobProgressTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is not Services.Operations.FileOperationJob job) return string.Empty;
|
||||
var parts = new List<string> { $"{job.CompletedItems:N0} / {job.TotalItems:N0} 个项目" };
|
||||
if (job.TotalBytes > 0)
|
||||
parts.Add($"{Models.FileEntry.FormatSize(job.CompletedBytes)} / {Models.FileEntry.FormatSize(job.TotalBytes)}");
|
||||
if (job.BytesPerSecond > 1) parts.Add($"{Models.FileEntry.FormatSize((long)job.BytesPerSecond)}/s");
|
||||
if (job.Eta is { } eta && eta.TotalSeconds > 1 && eta.TotalHours < 24)
|
||||
parts.Add($"剩余 {eta:mm\\:ss}");
|
||||
if (!string.IsNullOrEmpty(job.Error)) parts.Add(job.Error!);
|
||||
return string.Join(" · ", parts);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>作业 → 暂停/继续按钮图标。</summary>
|
||||
public sealed class JobPauseGlyphConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
=> value is Services.Operations.JobState.Paused ? "\uE768" : "\uE769";
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace FluidExplorer.Helpers;
|
||||
|
||||
/// <summary>DispatcherQueue 的 await 化封装:把 UI 线程更新变成可等待的操作,便于批量提交与顺序保证。</summary>
|
||||
public static class DispatcherQueueExtensions
|
||||
{
|
||||
public static Task EnqueueAsync(this DispatcherQueue queue, Action action)
|
||||
{
|
||||
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (!queue.TryEnqueue(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
tcs.TrySetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
}))
|
||||
{
|
||||
tcs.TrySetException(new InvalidOperationException("DispatcherQueue 已关闭,无法调度到 UI 线程。"));
|
||||
}
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static Task<T> EnqueueAsync<T>(this DispatcherQueue queue, Func<T> func)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (!queue.TryEnqueue(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
tcs.TrySetResult(func());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
}))
|
||||
{
|
||||
tcs.TrySetException(new InvalidOperationException("DispatcherQueue 已关闭,无法调度到 UI 线程。"));
|
||||
}
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>即发即忘的 UI 调度(不需要等待结果的场合)。</summary>
|
||||
public static void Post(this DispatcherQueue queue, Action action) => queue.TryEnqueue(() => action());
|
||||
}
|
||||
Reference in New Issue
Block a user