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

@@ -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>

View File

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

View File

@@ -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>()
{

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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>

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)
{
try