From 7489e0eb1cbf3c63d6c829c6ee7edfdff0149490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Sun, 6 Feb 2022 22:16:53 +0100 Subject: [PATCH] Timeline command visual navigation --- src/Core/FileTime.Core/Timeline/TimeRunner.cs | 22 ++- src/GuiApp/FileTime.Avalonia/App.axaml | 12 ++ .../FileTime.Avalonia/Models/RootDriveInfo.cs | 2 + .../ViewModels/MainPageViewModel.cs | 130 ++++++++++++++++-- .../ViewModels/ParalellCommandsViewModel.cs | 11 +- .../ViewModels/ParallelCommandViewModel.cs | 7 +- .../FileTime.Avalonia/Views/MainWindow.axaml | 101 +++++++------- .../Views/MainWindow.axaml.cs | 12 ++ 8 files changed, 227 insertions(+), 70 deletions(-) diff --git a/src/Core/FileTime.Core/Timeline/TimeRunner.cs b/src/Core/FileTime.Core/Timeline/TimeRunner.cs index 74b9b5e..3df058f 100644 --- a/src/Core/FileTime.Core/Timeline/TimeRunner.cs +++ b/src/Core/FileTime.Core/Timeline/TimeRunner.cs @@ -12,7 +12,9 @@ namespace FileTime.Core.Timeline private bool _resourceIsInUse; private readonly List _commandRunners = new(); - private bool _enableRunning = true; + private bool _enableRunning; //= true; + + private IReadOnlyList _parallelCommands = new List(); public bool EnableRunning { @@ -29,11 +31,17 @@ namespace FileTime.Core.Timeline } } - public IReadOnlyList ParallelCommands { get; private set; } = new List().AsReadOnly(); + public async Task> GetParallelCommandsAsync() + { + IReadOnlyList parallelCommands = new List(); + await RunWithLockAsync(() => parallelCommands = _parallelCommands); + + return parallelCommands; + } public AsyncEventHandler RefreshContainer { get; } = new AsyncEventHandler(); - 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) diff --git a/src/GuiApp/FileTime.Avalonia/App.axaml b/src/GuiApp/FileTime.Avalonia/App.axaml index 88d3774..ff4532d 100644 --- a/src/GuiApp/FileTime.Avalonia/App.axaml +++ b/src/GuiApp/FileTime.Avalonia/App.axaml @@ -163,6 +163,14 @@ + + + + @@ -170,5 +178,9 @@ + + diff --git a/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs b/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs index 268fb2d..d300cd5 100644 --- a/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs +++ b/src/GuiApp/FileTime.Avalonia/Models/RootDriveInfo.cs @@ -12,6 +12,8 @@ namespace FileTime.Avalonia.Models private readonly DriveInfo _driveInfo; private readonly IContainer _container; + public IContainer Container => _container; + [Property] private string _name; diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs index 049cd92..fe0e4da 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/MainPageViewModel.cs @@ -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(); 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(); 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(); + 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 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() { diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs index f78b0d8..d3b7907 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ParalellCommandsViewModel.cs @@ -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 ParallelCommands { get; } + + [Property] + private ObservableCollection _parallelCommands; public ushort Id { get; } public ParallelCommandsViewModel(ReadOnlyParallelCommands parallelCommands) { - ParallelCommands = parallelCommands.Commands.Select(c => new ParallelCommandViewModel(c)).ToList().AsReadOnly(); + _parallelCommands = new ObservableCollection(parallelCommands.Commands.Select(c => new ParallelCommandViewModel(c))); Id = parallelCommands.Id; } diff --git a/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs b/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs index 6248434..706fb68 100644 --- a/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs +++ b/src/GuiApp/FileTime.Avalonia/ViewModels/ParallelCommandViewModel.cs @@ -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; diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml index 7dd5fa6..61026a0 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml @@ -53,60 +53,61 @@ Items="{Binding RootDriveInfos}"> - - - - - - + + + Source="{SvgImage /Assets/material/folder.svg}" /> - - + HorizontalAlignment="Stretch" + Orientation="Horizontal"> - + - - + + - - + - - - + + - + + + + + + + + + - @@ -160,10 +161,12 @@ - - - - + + + + + + diff --git a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs index 099c526..2402c0c 100644 --- a/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs +++ b/src/GuiApp/FileTime.Avalonia/Views/MainWindow.axaml.cs @@ -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