Timeline command visual navigation

This commit is contained in:
2022-02-06 22:16:53 +01:00
parent dfe44cfb16
commit 7489e0eb1c
8 changed files with 227 additions and 70 deletions

View File

@@ -12,7 +12,9 @@ namespace FileTime.Core.Timeline
private bool _resourceIsInUse; private bool _resourceIsInUse;
private readonly List<Thread> _commandRunners = new(); private readonly List<Thread> _commandRunners = new();
private bool _enableRunning = true; private bool _enableRunning; //= true;
private IReadOnlyList<ReadOnlyParallelCommands> _parallelCommands = new List<ReadOnlyParallelCommands>();
public bool EnableRunning 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 AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
public event EventHandler? CommandsChanged; public AsyncEventHandler CommandsChangedAsync { get; } = new();
public TimeRunner(CommandExecutor commandExecutor) public TimeRunner(CommandExecutor commandExecutor)
{ {
@@ -162,12 +170,12 @@ namespace FileTime.Core.Timeline
private async Task RefreshCommands(PointInTime? fullStartTime = null) 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; var startIndex = fullStartTime == null ? 1 : 0;
for (var i = startIndex; i < _commandsToRun.Count; i++) 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; var wait = false;
await RunWithLockAsync(() => wait = _commandsToRun.Count == 1); await RunWithLockAsync(() => wait = _commandsToRun.Count == 1);
if (wait) await Task.Delay(100); if (wait) await Task.Delay(100);
await RunWithLockAsync(() => ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly()); await RunWithLockAsync(() => _parallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly());
CommandsChanged?.Invoke(this, EventArgs.Empty); await CommandsChangedAsync.InvokeAsync(this, AsyncEventArgs.Empty);
} }
private async Task RunWithLockAsync(Action action) private async Task RunWithLockAsync(Action action)

View File

@@ -163,6 +163,14 @@
</Setter> </Setter>
</Style> </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"> <Style Selector="Grid.PlacesItem">
<Setter Property="Background" Value="#01000000"/> <Setter Property="Background" Value="#01000000"/>
</Style> </Style>
@@ -170,5 +178,9 @@
<Style Selector="Grid.PlacesItem:pointerover"> <Style Selector="Grid.PlacesItem:pointerover">
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/> <Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
</Style> </Style>
<Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
</Style>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -12,6 +12,8 @@ namespace FileTime.Avalonia.Models
private readonly DriveInfo _driveInfo; private readonly DriveInfo _driveInfo;
private readonly IContainer _container; private readonly IContainer _container;
public IContainer Container => _container;
[Property] [Property]
private string _name; private string _name;

View File

@@ -27,6 +27,8 @@ using Syroot.Windows.IO;
using FileTime.Avalonia.IconProviders; using FileTime.Avalonia.IconProviders;
using Avalonia.Threading; using Avalonia.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using AsyncEvent;
using System.Threading;
namespace FileTime.Avalonia.ViewModels namespace FileTime.Avalonia.ViewModels
{ {
@@ -99,7 +101,7 @@ namespace FileTime.Avalonia.ViewModels
App.ServiceProvider.GetService<TopContainer>(); App.ServiceProvider.GetService<TopContainer>();
await StatePersistence.LoadStatesAsync(); await StatePersistence.LoadStatesAsync();
_timeRunner.CommandsChanged += UpdateParalellCommands; _timeRunner.CommandsChangedAsync.Add(UpdateParalellCommands);
InitCommandBindings(); InitCommandBindings();
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) }); _keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) });
@@ -211,9 +213,11 @@ namespace FileTime.Avalonia.ViewModels
_logger?.LogInformation($"{nameof(MainPageViewModel)} initialized."); _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)) if (!TimelineCommands.Any(c => c.Id == parallelCommand.Id))
{ {
@@ -223,7 +227,7 @@ namespace FileTime.Avalonia.ViewModels
var itemsToRemove = new List<ParallelCommandsViewModel>(); var itemsToRemove = new List<ParallelCommandsViewModel>();
foreach (var parallelCommandVm in TimelineCommands) 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); itemsToRemove.Add(parallelCommandVm);
} }
@@ -234,6 +238,33 @@ namespace FileTime.Avalonia.ViewModels
itemsToRemove[i].Dispose(); itemsToRemove[i].Dispose();
TimelineCommands.Remove(itemsToRemove[i]); 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) private async Task<IContainer?> GetContainerForWindowsDrive(DriveInfo drive)
@@ -771,6 +802,66 @@ namespace FileTime.Avalonia.ViewModels
return Task.CompletedTask; 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] [Command]
public async void ProcessInputs() public async void ProcessInputs()
{ {
@@ -1195,18 +1286,37 @@ namespace FileTime.Avalonia.ViewModels
FileTime.App.Core.Command.Commands.Dummy, FileTime.App.Core.Command.Commands.Dummy,
new KeyWithModifiers[] { new KeyWithModifiers(Key.D4, shift: true) }, new KeyWithModifiers[] { new KeyWithModifiers(Key.D4, shift: true) },
RunCommandInContainer), 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 //TODO REMOVE
new CommandBinding( new CommandBinding(
"open in default file browser", "open in default file browser",
FileTime.App.Core.Command.Commands.Dummy, FileTime.App.Core.Command.Commands.Dummy,
new KeyWithModifiers[] { new KeyWithModifiers(Key.O), new KeyWithModifiers(Key.E) }, new KeyWithModifiers[] { new KeyWithModifiers(Key.O), new KeyWithModifiers(Key.E) },
OpenInDefaultFileExplorer), 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>() var universalCommandBindings = new List<CommandBinding>()
{ {

View File

@@ -2,18 +2,23 @@
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using MvvmGen;
using System.Collections.ObjectModel;
namespace FileTime.Avalonia.ViewModels namespace FileTime.Avalonia.ViewModels
{ {
public class ParallelCommandsViewModel : IDisposable [ViewModel]
public partial class ParallelCommandsViewModel : IDisposable
{ {
private bool _disposed; private bool _disposed;
public IReadOnlyCollection<ParallelCommandViewModel> ParallelCommands { get; }
[Property]
private ObservableCollection<ParallelCommandViewModel> _parallelCommands;
public ushort Id { get; } public ushort Id { get; }
public ParallelCommandsViewModel(ReadOnlyParallelCommands parallelCommands) 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; Id = parallelCommands.Id;
} }

View File

@@ -12,11 +12,16 @@ namespace FileTime.Avalonia.ViewModels
public partial class ParallelCommandViewModel : IDisposable public partial class ParallelCommandViewModel : IDisposable
{ {
private bool _disposed; private bool _disposed;
private readonly ReadOnlyCommandTimeState _commandTimeState;
[Property]
private ReadOnlyCommandTimeState _commandTimeState;
[Property] [Property]
private int _progress; private int _progress;
[Property]
private bool _isSelected;
public CanCommandRun CanRun => _commandTimeState.CanRun; public CanCommandRun CanRun => _commandTimeState.CanRun;
public bool ForceRun => _commandTimeState.ForceRun; public bool ForceRun => _commandTimeState.ForceRun;

View File

@@ -53,6 +53,7 @@
Items="{Binding RootDriveInfos}"> Items="{Binding RootDriveInfos}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid Classes="RootDriveInfo" PointerPressed="OnRootDrivePointerPressed" Cursor="Hand">
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto"> <Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto">
<Image <Image
Grid.RowSpan="2" Grid.RowSpan="2"
@@ -106,7 +107,7 @@
Maximum="100" Maximum="100"
Value="{Binding UsedPercentage}" /> Value="{Binding UsedPercentage}" />
</Grid> </Grid>
</Grid>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
</ItemsRepeater> </ItemsRepeater>
@@ -160,10 +161,12 @@
<ItemsControl Items="{Binding ParallelCommands}"> <ItemsControl Items="{Binding ParallelCommands}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border BorderThickness="1" Classes="TimelineCommand" Classes.SelectedTimelineCommand="{Binding IsSelected}">
<StackPanel> <StackPanel>
<TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Name}"/>
<ProgressBar Margin="0,5,0,0" Maximum="100" Value="{Binding Progress}"/> <ProgressBar Margin="0,5,0,0" Maximum="100" Value="{Binding Progress}"/>
</StackPanel> </StackPanel>
</Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>

View File

@@ -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) private void OnWindowClosed(object sender, EventArgs e)
{ {
try try