Timeline command visual navigation
This commit is contained in:
@@ -12,7 +12,9 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
private bool _resourceIsInUse;
|
||||
private readonly List<Thread> _commandRunners = new();
|
||||
private bool _enableRunning = true;
|
||||
private bool _enableRunning; //= true;
|
||||
|
||||
private IReadOnlyList<ReadOnlyParallelCommands> _parallelCommands = new List<ReadOnlyParallelCommands>();
|
||||
|
||||
public bool EnableRunning
|
||||
{
|
||||
@@ -29,11 +31,17 @@ namespace FileTime.Core.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
|
||||
public async Task<IReadOnlyList<ReadOnlyParallelCommands>> GetParallelCommandsAsync()
|
||||
{
|
||||
IReadOnlyList<ReadOnlyParallelCommands> parallelCommands = new List<ReadOnlyParallelCommands>();
|
||||
await RunWithLockAsync(() => parallelCommands = _parallelCommands);
|
||||
|
||||
return parallelCommands;
|
||||
}
|
||||
|
||||
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
|
||||
|
||||
public event EventHandler? CommandsChanged;
|
||||
public AsyncEventHandler CommandsChangedAsync { get; } = new();
|
||||
|
||||
public TimeRunner(CommandExecutor commandExecutor)
|
||||
{
|
||||
@@ -162,12 +170,12 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
private async Task RefreshCommands(PointInTime? fullStartTime = null)
|
||||
{
|
||||
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var currentTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var startIndex = fullStartTime == null ? 1 : 0;
|
||||
|
||||
for (var i = startIndex; i < _commandsToRun.Count; i++)
|
||||
{
|
||||
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
|
||||
currentTime = await _commandsToRun[i].RefreshResult(currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +184,8 @@ namespace FileTime.Core.Timeline
|
||||
var wait = false;
|
||||
await RunWithLockAsync(() => wait = _commandsToRun.Count == 1);
|
||||
if (wait) await Task.Delay(100);
|
||||
await RunWithLockAsync(() => ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly());
|
||||
CommandsChanged?.Invoke(this, EventArgs.Empty);
|
||||
await RunWithLockAsync(() => _parallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly());
|
||||
await CommandsChangedAsync.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Action action)
|
||||
|
||||
@@ -163,6 +163,14 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.RootDriveInfo">
|
||||
<Setter Property="Background" Value="#01000000"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.RootDriveInfo:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.PlacesItem">
|
||||
<Setter Property="Background" Value="#01000000"/>
|
||||
</Style>
|
||||
@@ -170,5 +178,9 @@
|
||||
<Style Selector="Grid.PlacesItem:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.SelectedTimelineCommand">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace FileTime.Avalonia.Models
|
||||
private readonly DriveInfo _driveInfo;
|
||||
private readonly IContainer _container;
|
||||
|
||||
public IContainer Container => _container;
|
||||
|
||||
[Property]
|
||||
private string _name;
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ using Syroot.Windows.IO;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AsyncEvent;
|
||||
using System.Threading;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -99,7 +101,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
App.ServiceProvider.GetService<TopContainer>();
|
||||
await StatePersistence.LoadStatesAsync();
|
||||
|
||||
_timeRunner.CommandsChanged += UpdateParalellCommands;
|
||||
_timeRunner.CommandsChangedAsync.Add(UpdateParalellCommands);
|
||||
InitCommandBindings();
|
||||
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) });
|
||||
@@ -211,9 +213,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_logger?.LogInformation($"{nameof(MainPageViewModel)} initialized.");
|
||||
}
|
||||
|
||||
private void UpdateParalellCommands(object? sender, EventArgs e)
|
||||
private async Task UpdateParalellCommands(object? sender, AsyncEventArgs e, CancellationToken token)
|
||||
{
|
||||
foreach (var parallelCommand in _timeRunner.ParallelCommands)
|
||||
var parallelCommands = await _timeRunner.GetParallelCommandsAsync();
|
||||
|
||||
foreach (var parallelCommand in parallelCommands)
|
||||
{
|
||||
if (!TimelineCommands.Any(c => c.Id == parallelCommand.Id))
|
||||
{
|
||||
@@ -223,7 +227,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
var itemsToRemove = new List<ParallelCommandsViewModel>();
|
||||
foreach (var parallelCommandVm in TimelineCommands)
|
||||
{
|
||||
if (!_timeRunner.ParallelCommands.Any(c => c.Id == parallelCommandVm.Id))
|
||||
if (!parallelCommands.Any(c => c.Id == parallelCommandVm.Id))
|
||||
{
|
||||
itemsToRemove.Add(parallelCommandVm);
|
||||
}
|
||||
@@ -234,6 +238,33 @@ namespace FileTime.Avalonia.ViewModels
|
||||
itemsToRemove[i].Dispose();
|
||||
TimelineCommands.Remove(itemsToRemove[i]);
|
||||
}
|
||||
|
||||
foreach (var parallelCommand in parallelCommands)
|
||||
{
|
||||
var parallelCommandsVM = TimelineCommands.First(t => t.Id == parallelCommand.Id);
|
||||
foreach (var command in parallelCommand.Commands)
|
||||
{
|
||||
if (!parallelCommandsVM.ParallelCommands.Any(c => c.CommandTimeState.Command == command.Command))
|
||||
{
|
||||
parallelCommandsVM.ParallelCommands.Add(new ParallelCommandViewModel(command));
|
||||
}
|
||||
}
|
||||
|
||||
var commandVMsToRemove = new List<ParallelCommandViewModel>();
|
||||
foreach (var commandVM in parallelCommandsVM.ParallelCommands)
|
||||
{
|
||||
if (!parallelCommand.Commands.Any(c => c.Command == commandVM.CommandTimeState.Command))
|
||||
{
|
||||
commandVMsToRemove.Add(commandVM);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < commandVMsToRemove.Count; i++)
|
||||
{
|
||||
commandVMsToRemove[i].Dispose();
|
||||
parallelCommandsVM.ParallelCommands.Remove(commandVMsToRemove[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IContainer?> GetContainerForWindowsDrive(DriveInfo drive)
|
||||
@@ -771,6 +802,66 @@ namespace FileTime.Avalonia.ViewModels
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectPreviousTimelineBlock()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectNextTimelineCommand()
|
||||
{
|
||||
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
|
||||
if (currentSelected == null) return Task.CompletedTask;
|
||||
|
||||
ParallelCommandViewModel? lastCommand = null;
|
||||
var any = false;
|
||||
foreach (var command in TimelineCommands.SelectMany(t => t.ParallelCommands))
|
||||
{
|
||||
var isSelected = lastCommand == currentSelected;
|
||||
command.IsSelected = isSelected;
|
||||
any = any || isSelected;
|
||||
lastCommand = command;
|
||||
}
|
||||
if (!any && lastCommand != null) lastCommand.IsSelected = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectPreviousTimelineCommand()
|
||||
{
|
||||
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
|
||||
if (currentSelected == null) return Task.CompletedTask;
|
||||
|
||||
ParallelCommandViewModel? lastCommand = null;
|
||||
foreach (var command in TimelineCommands.SelectMany(t => t.ParallelCommands))
|
||||
{
|
||||
if (lastCommand != null)
|
||||
{
|
||||
lastCommand.IsSelected = command == currentSelected;
|
||||
}
|
||||
lastCommand = command;
|
||||
}
|
||||
if (lastCommand != null) lastCommand.IsSelected = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SelectNextTimelineBlock()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private ParallelCommandViewModel? GetSelectedTimelineCommandOrSelectFirst()
|
||||
{
|
||||
var currentSelected = TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault(c => c.IsSelected);
|
||||
if (currentSelected != null) return currentSelected;
|
||||
|
||||
var firstCommand = TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault();
|
||||
if (firstCommand != null)
|
||||
{
|
||||
firstCommand.IsSelected = true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public async void ProcessInputs()
|
||||
{
|
||||
@@ -1195,18 +1286,37 @@ namespace FileTime.Avalonia.ViewModels
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.D4, shift: true) },
|
||||
RunCommandInContainer),
|
||||
new CommandBinding(
|
||||
"copy path",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.C), new KeyWithModifiers(Key.P) },
|
||||
CopyPath),
|
||||
new CommandBinding(
|
||||
"select previous timeline block",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.H) },
|
||||
SelectPreviousTimelineBlock),
|
||||
new CommandBinding(
|
||||
"select next timeline command",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.J) },
|
||||
SelectNextTimelineCommand),
|
||||
new CommandBinding(
|
||||
"select previous timeline command",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.K) },
|
||||
SelectPreviousTimelineCommand),
|
||||
new CommandBinding(
|
||||
"select next timeline block",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.L) },
|
||||
SelectNextTimelineBlock),
|
||||
//TODO REMOVE
|
||||
new CommandBinding(
|
||||
"open in default file browser",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.O), new KeyWithModifiers(Key.E) },
|
||||
OpenInDefaultFileExplorer),
|
||||
//TODO REMOVE
|
||||
new CommandBinding(
|
||||
"copy path",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.C), new KeyWithModifiers(Key.P) },
|
||||
CopyPath),
|
||||
};
|
||||
var universalCommandBindings = new List<CommandBinding>()
|
||||
{
|
||||
|
||||
@@ -2,18 +2,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using FileTime.Core.Timeline;
|
||||
using MvvmGen;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
public class ParallelCommandsViewModel : IDisposable
|
||||
[ViewModel]
|
||||
public partial class ParallelCommandsViewModel : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
public IReadOnlyCollection<ParallelCommandViewModel> ParallelCommands { get; }
|
||||
|
||||
[Property]
|
||||
private ObservableCollection<ParallelCommandViewModel> _parallelCommands;
|
||||
public ushort Id { get; }
|
||||
|
||||
public ParallelCommandsViewModel(ReadOnlyParallelCommands parallelCommands)
|
||||
{
|
||||
ParallelCommands = parallelCommands.Commands.Select(c => new ParallelCommandViewModel(c)).ToList().AsReadOnly();
|
||||
_parallelCommands = new ObservableCollection<ParallelCommandViewModel>(parallelCommands.Commands.Select(c => new ParallelCommandViewModel(c)));
|
||||
Id = parallelCommands.Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
public partial class ParallelCommandViewModel : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly ReadOnlyCommandTimeState _commandTimeState;
|
||||
|
||||
[Property]
|
||||
private ReadOnlyCommandTimeState _commandTimeState;
|
||||
|
||||
[Property]
|
||||
private int _progress;
|
||||
|
||||
[Property]
|
||||
private bool _isSelected;
|
||||
|
||||
public CanCommandRun CanRun => _commandTimeState.CanRun;
|
||||
public bool ForceRun => _commandTimeState.ForceRun;
|
||||
|
||||
|
||||
@@ -53,60 +53,61 @@
|
||||
Items="{Binding RootDriveInfos}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto">
|
||||
<Image
|
||||
Grid.RowSpan="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
<Grid Classes="RootDriveInfo" PointerPressed="OnRootDrivePointerPressed" Cursor="Hand">
|
||||
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto">
|
||||
<Image
|
||||
Grid.RowSpan="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding FullName}" />
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Classes="ExtraSmallText"
|
||||
Text="{Binding Label}" IsVisible="{Binding Label,Converter={StaticResource IsNotEmptyConverter}}" />
|
||||
</StackPanel>
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding FullName}" />
|
||||
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Classes="ExtraSmallText"
|
||||
Text="{Binding Label}" IsVisible="{Binding Label,Converter={StaticResource IsNotEmptyConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text=" / ">
|
||||
</TextBlock>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
|
||||
<ProgressBar
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
MinWidth="100"
|
||||
HorizontalAlignment="Stretch"
|
||||
Maximum="100"
|
||||
Value="{Binding UsedPercentage}" />
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text=" / ">
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
MinWidth="100"
|
||||
HorizontalAlignment="Stretch"
|
||||
Maximum="100"
|
||||
Value="{Binding UsedPercentage}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
@@ -160,10 +161,12 @@
|
||||
<ItemsControl Items="{Binding ParallelCommands}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<ProgressBar Margin="0,5,0,0" Maximum="100" Value="{Binding Progress}"/>
|
||||
</StackPanel>
|
||||
<Border BorderThickness="1" Classes="TimelineCommand" Classes.SelectedTimelineCommand="{Binding IsSelected}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<ProgressBar Margin="0,5,0,0" Maximum="100" Value="{Binding Progress}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -106,6 +106,18 @@ namespace FileTime.Avalonia.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRootDrivePointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.Handled
|
||||
&& ViewModel != null
|
||||
&& e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
|
||||
&& sender is StyledElement control
|
||||
&& control.DataContext is RootDriveInfo rootDriveInfo)
|
||||
{
|
||||
ViewModel.OpenContainer(rootDriveInfo.Container);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
private void OnWindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user