From 30ecc6e5154658c80be86901c8f9b891dc1b56fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Tue, 31 Jan 2023 23:26:36 +0100 Subject: [PATCH] WIP CommandScheduler UI --- .../PauseCommandSchedulerCommand.cs | 13 + .../StartCommandSchedulerCommand.cs | 13 + .../ViewModels/IAppState.cs | 4 +- .../Timeline/ICommandTimeStateViewModel.cs | 8 + .../Timeline/IParallelCommandsViewModel.cs | 9 + .../ViewModels/Timeline/ITimelineViewModel.cs | 8 + ...mmandSchedulerUserCommandHandlerService.cs | 25 ++ ...emManipulationUserCommandHandlerService.cs | 2 +- src/AppCommon/FileTime.App.Core/Startup.cs | 5 +- ...faultIdentifiableCommandHandlerRegister.cs | 2 + .../ViewModels/AppStateBase.cs | 7 +- .../Timeline/CommandTimeStateViewModel.cs | 21 ++ .../Timeline/ParallelCommandsViewModel.cs | 19 ++ .../ViewModels/Timeline/TimelineViewModel.cs | 20 ++ .../Command/ExecutionState.cs | 1 + .../Command/ICommand.cs | 4 + .../Extensions/BindedCollectionExtensions.cs | 27 ++ .../Extensions/DynamicDataExtensions.cs | 2 +- .../Models/BindedCollection.cs | 2 +- .../Timeline/CommandTimeState.cs | 34 ++- .../Timeline/ICommandScheduler.cs | 4 + .../Timeline/ParallelCommands.cs | 49 +++- src/Core/FileTime.Core.Command/CommandBase.cs | 36 +++ .../FileTime.Core.Command/Copy/CopyCommand.cs | 7 +- .../Create/CreateItemBase.cs | 9 +- .../Delete/DeleteCommand.cs | 17 +- .../CommandScheduler.cs | 49 ++-- .../LocalCommandExecutor.cs | 6 +- .../Configuration/MainConfiguration.cs | 5 +- .../ViewModels/IGuiAppState.cs | 1 + .../ViewModels/GuiAppState.cs | 11 +- .../FileTime.GuiApp/Resources/Styles.axaml | 21 +- .../Services/DefaultModeKeyInputHandler.cs | 5 +- .../RapidTravelModeKeyInputHandler.cs | 3 +- .../Services/RootDriveInfoService.cs | 5 +- .../FileTime.GuiApp/Views/MainWindow.axaml | 254 +++++++++++------- .../LocalContentProvider.cs | 26 +- 37 files changed, 553 insertions(+), 181 deletions(-) create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/PauseCommandSchedulerCommand.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/StartCommandSchedulerCommand.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/IParallelCommandsViewModel.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ITimelineViewModel.cs create mode 100644 src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs create mode 100644 src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs create mode 100644 src/AppCommon/FileTime.App.Core/ViewModels/Timeline/ParallelCommandsViewModel.cs create mode 100644 src/AppCommon/FileTime.App.Core/ViewModels/Timeline/TimelineViewModel.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Extensions/BindedCollectionExtensions.cs rename src/{AppCommon/FileTime.App.Core.Abstraction => Core/FileTime.Core.Abstraction}/Models/BindedCollection.cs (98%) create mode 100644 src/Core/FileTime.Core.Command/CommandBase.cs diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/PauseCommandSchedulerCommand.cs b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/PauseCommandSchedulerCommand.cs new file mode 100644 index 0000000..f897d5d --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/PauseCommandSchedulerCommand.cs @@ -0,0 +1,13 @@ +namespace FileTime.App.Core.UserCommand; + +public class PauseCommandSchedulerCommand : IIdentifiableUserCommand +{ + public const string CommandName = "pause_command_scheduler"; + public static PauseCommandSchedulerCommand Instance { get; } = new(); + + private PauseCommandSchedulerCommand() + { + } + + public string UserCommandID => CommandName; +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/StartCommandSchedulerCommand.cs b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/StartCommandSchedulerCommand.cs new file mode 100644 index 0000000..f9941be --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/UserCommand/StartCommandSchedulerCommand.cs @@ -0,0 +1,13 @@ +namespace FileTime.App.Core.UserCommand; + +public sealed class StartCommandSchedulerCommand : IIdentifiableUserCommand +{ + public const string CommandName = "start_command_scheduler"; + public static StartCommandSchedulerCommand Instance { get; } = new(); + + private StartCommandSchedulerCommand() + { + } + + public string UserCommandID => CommandName; +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs index 5e0725f..f00f379 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; -using System.Reactive.Subjects; using FileTime.App.Core.Models.Enums; -using FileTime.Core.Timeline; +using FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels; @@ -13,6 +12,7 @@ public interface IAppState IObservable ViewMode { get; } string RapidTravelText { get; set; } ITabViewModel? CurrentSelectedTab { get; } + ITimelineViewModel TimelineViewModel { get; } void AddTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel); diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs new file mode 100644 index 0000000..947e820 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ICommandTimeStateViewModel.cs @@ -0,0 +1,8 @@ +namespace FileTime.App.Core.ViewModels.Timeline; + +public interface ICommandTimeStateViewModel +{ + IObservable TotalProgress { get; } + IObservable DisplayLabel { get; } + IObservable IsSelected { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/IParallelCommandsViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/IParallelCommandsViewModel.cs new file mode 100644 index 0000000..0580987 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/IParallelCommandsViewModel.cs @@ -0,0 +1,9 @@ +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.App.Core.ViewModels.Timeline; + +public interface IParallelCommandsViewModel +{ + BindedCollection Commands { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ITimelineViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ITimelineViewModel.cs new file mode 100644 index 0000000..bb242ca --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/Timeline/ITimelineViewModel.cs @@ -0,0 +1,8 @@ +using FileTime.Core.Models; + +namespace FileTime.App.Core.ViewModels.Timeline; + +public interface ITimelineViewModel +{ + BindedCollection ParallelCommandsGroups { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs new file mode 100644 index 0000000..77cdf8e --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/CommandSchedulerUserCommandHandlerService.cs @@ -0,0 +1,25 @@ +using FileTime.App.Core.UserCommand; +using FileTime.Core.Timeline; + +namespace FileTime.App.Core.Services.UserCommandHandler; + +public class CommandSchedulerUserCommandHandlerService : UserCommandHandlerServiceBase +{ + private readonly ICommandScheduler _commandScheduler; + + public CommandSchedulerUserCommandHandlerService(ICommandScheduler commandScheduler) + { + _commandScheduler = commandScheduler; + AddCommandHandlers(new IUserCommandHandler[] + { + new TypeUserCommandHandler(PauseCommandScheduler), + new TypeUserCommandHandler(StartCommandScheduler), + }); + } + + private async Task PauseCommandScheduler() + => await _commandScheduler.SetRunningEnabledAsync(false); + + private async Task StartCommandScheduler() + => await _commandScheduler.SetRunningEnabledAsync(true); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index b8c035d..9a48128 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -52,7 +52,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi SaveCurrentLocation(l => _currentLocation = l); SaveCurrentSelectedItem(i => _currentSelectedItem = i); - _markedItems = new BindedCollection(appState.SelectedTab.Select(t => t?.MarkedItems)); + _markedItems = appState.SelectedTab.Select(t => t?.MarkedItems).ToBindedCollection(); AddCommandHandlers(new IUserCommandHandler[] { diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index 7be65af..e4ecf20 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -3,6 +3,7 @@ using FileTime.App.Core.Services.UserCommandHandler; using FileTime.App.Core.StartupServices; using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels.ItemPreview; +using FileTime.App.Core.ViewModels.Timeline; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -23,6 +24,7 @@ public static class Startup serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); return serviceCollection .AddCommandHandlers() @@ -35,6 +37,7 @@ public static class Startup return serviceCollection .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs index 3623b33..7771052 100644 --- a/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs +++ b/src/AppCommon/FileTime.App.Core/StartupServices/DefaultIdentifiableCommandHandlerRegister.cs @@ -37,7 +37,9 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler AddUserCommand(PasteCommand.Merge); AddUserCommand(PasteCommand.Overwrite); AddUserCommand(PasteCommand.Skip); + AddUserCommand(PauseCommandSchedulerCommand.Instance); AddUserCommand(RefreshCommand.Instance); + AddUserCommand(StartCommandSchedulerCommand.Instance); AddUserCommand(SwitchToTabCommand.SwitchToLastTab); AddUserCommand(SwitchToTabCommand.SwitchToTab1); AddUserCommand(SwitchToTabCommand.SwitchToTab2); diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs index a2236a3..4bc3cce 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs @@ -3,12 +3,14 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; using FileTime.App.Core.Models.Enums; +using FileTime.App.Core.ViewModels.Timeline; using MvvmGen; using MoreLinq; namespace FileTime.App.Core.ViewModels; [ViewModel] +[Inject(typeof(ITimelineViewModel), "TimelineViewModel", PropertyAccessModifier = AccessModifier.Public)] public abstract partial class AppStateBase : IAppState { private readonly BehaviorSubject _searchText = new(null); @@ -76,7 +78,10 @@ public abstract partial class AppStateBase : IAppState private ITabViewModel? GetSelectedTab(IEnumerable tabs, ITabViewModel? expectedSelectedTab) { - var (preferred, others) = tabs.OrderBy(t => t.TabNumber).Partition(t => t.TabNumber >= (expectedSelectedTab?.TabNumber ?? 0)); + var (preferred, others) = + tabs + .OrderBy(t => t.TabNumber) + .Partition(t => t.TabNumber >= (expectedSelectedTab?.TabNumber ?? 0)); return preferred.Concat(others.Reverse()).FirstOrDefault(); } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs new file mode 100644 index 0000000..99e229d --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/CommandTimeStateViewModel.cs @@ -0,0 +1,21 @@ +using System.Reactive.Subjects; +using FileTime.Core.Timeline; + +namespace FileTime.App.Core.ViewModels.Timeline; + +public class CommandTimeStateViewModel : ICommandTimeStateViewModel +{ + public IObservable TotalProgress { get; } + + public IObservable DisplayLabel { get; } + + public IObservable IsSelected { get; } + + public CommandTimeStateViewModel(CommandTimeState commandTimeState) + { + DisplayLabel = commandTimeState.Command.DisplayLabel; + TotalProgress = commandTimeState.Command.TotalProgress; + //TODO + IsSelected = new BehaviorSubject(false); + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/ParallelCommandsViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/ParallelCommandsViewModel.cs new file mode 100644 index 0000000..e2567a6 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/ParallelCommandsViewModel.cs @@ -0,0 +1,19 @@ +using DynamicData.Alias; +using FileTime.Core.Extensions; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.App.Core.ViewModels.Timeline; + +public class ParallelCommandsViewModel : IParallelCommandsViewModel +{ + public BindedCollection Commands { get; } + + public ParallelCommandsViewModel(ParallelCommands parallelCommands) + { + Commands = parallelCommands + .Commands + .Select(c => new CommandTimeStateViewModel(c) as ICommandTimeStateViewModel) + .ToBindedCollection(); + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/TimelineViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/TimelineViewModel.cs new file mode 100644 index 0000000..88c16ae --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/ViewModels/Timeline/TimelineViewModel.cs @@ -0,0 +1,20 @@ +using DynamicData.Alias; +using FileTime.Core.Extensions; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.App.Core.ViewModels.Timeline; + +public class TimelineViewModel : ITimelineViewModel +{ + public BindedCollection ParallelCommandsGroups { get; } + + public TimelineViewModel(ICommandScheduler commandScheduler) + { + ParallelCommandsGroups = + commandScheduler + .CommandsToRun + .Select(p => new ParallelCommandsViewModel(p) as IParallelCommandsViewModel) + .ToBindedCollection(); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Command/ExecutionState.cs b/src/Core/FileTime.Core.Abstraction/Command/ExecutionState.cs index 584be10..47b9c3b 100644 --- a/src/Core/FileTime.Core.Abstraction/Command/ExecutionState.cs +++ b/src/Core/FileTime.Core.Abstraction/Command/ExecutionState.cs @@ -2,6 +2,7 @@ namespace FileTime.Core.Command; public enum ExecutionState { + Initializing, Waiting, Running, Finished diff --git a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs index 6b494f2..338dd54 100644 --- a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs +++ b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs @@ -4,6 +4,10 @@ namespace FileTime.Core.Command; public interface ICommand { + IObservable DisplayLabel { get; } + IObservable TotalProgress { get; } + IObservable CurrentProgress { get; } + Task CanRun(PointInTime currentTime); Task SimulateCommand(PointInTime currentTime); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/BindedCollectionExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/BindedCollectionExtensions.cs new file mode 100644 index 0000000..24d0eef --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Extensions/BindedCollectionExtensions.cs @@ -0,0 +1,27 @@ +using DynamicData; +using FileTime.Core.Models; + +namespace FileTime.Core.Extensions; + +public static class BindedCollectionExtensions +{ + public static BindedCollection ToBindedCollection(this IObservable> source) + { + return new BindedCollection(source); + } + + public static BindedCollection ToBindedCollection(this IObservable>?> source) + { + return new BindedCollection(source); + } + + public static BindedCollection ToBindedCollection(this IObservable> source) where TKey : notnull + { + return new BindedCollection(source); + } + + public static BindedCollection ToBindedCollection(this IObservable>?> source) where TKey : notnull + { + return new BindedCollection(source); + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs index 7b4d1aa..08c6057 100644 --- a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs +++ b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs @@ -53,7 +53,7 @@ public static class DynamicDataExtensions this IObservable> stream) => await GetItemsAsync(stream.ToCollection()); - private static Task?> GetItemsAsync( + public static Task?> GetItemsAsync( this IObservable> stream) { var taskCompletionSource = new TaskCompletionSource?>(); diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs b/src/Core/FileTime.Core.Abstraction/Models/BindedCollection.cs similarity index 98% rename from src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs rename to src/Core/FileTime.Core.Abstraction/Models/BindedCollection.cs index 9b2140c..cadc1b7 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/BindedCollection.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/BindedCollection.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using DynamicData; using PropertyChanged.SourceGenerator; -namespace FileTime.App.Core.Models; +namespace FileTime.Core.Models; public partial class BindedCollection : IDisposable, INotifyPropertyChanged { diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs index a0f27bd..150bb44 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs @@ -4,18 +4,50 @@ namespace FileTime.Core.Timeline; public class CommandTimeState { + private object _executionStateLock = new object(); + private ExecutionState _executionState; public ICommand Command { get; } public CanCommandRun CanRun { get; private set; } = CanCommandRun.False; public bool ForceRun { get; set; } - public ExecutionState ExecutionState { get; set; } + + public ExecutionState ExecutionState + { + get + { + lock (_executionStateLock) + { + return _executionState; + } + } + set + { + lock (_executionStateLock) + { + _executionState = value; + } + } + } public CommandTimeState(ICommand command) { Command = command; + SetToWait(); } public async Task UpdateStateAsync(PointInTime? startPoint) { CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint); } + + private async void SetToWait() + { + // This command won't be shown in the scheduler until this timeout is over + // Short living commands while not "blink on" the list + await Task.Delay(100); + lock (_executionStateLock) + { + if (_executionState == ExecutionState.Initializing) + _executionState = ExecutionState.Waiting; + } + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs index e9d6ad6..84c2249 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ICommandScheduler.cs @@ -1,3 +1,4 @@ +using DynamicData; using FileTime.Core.Command; using FileTime.Core.Models; @@ -7,5 +8,8 @@ public interface ICommandScheduler { Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false); IObservable ContainerToRefresh { get; } + IObservable> CommandsToRun { get; } + bool IsRunningEnabled { get; } void RefreshContainer(FullName container); + Task SetRunningEnabledAsync(bool value); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs index 835eee6..85c90b1 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs @@ -1,15 +1,32 @@ +using DynamicData; using FileTime.Core.Command; +using FileTime.Core.Extensions; +using FileTime.Core.Models; namespace FileTime.Core.Timeline; -public class ParallelCommands +public class ParallelCommands : IDisposable { private static ushort _idCounter; - private List _commands; + private readonly SourceList _commands; + private PointInTime? _startTime; + public ushort Id { get; } - public IReadOnlyList Commands { get; } + + public IObservable> Commands { get; } + + public BindedCollection CommandsCollection { get; } + public PointInTime? Result { get; private set; } + public PointInTime? StartTime => _startTime; + + public async Task SetStartTimeAsync(PointInTime? startTime) + { + _startTime = startTime; + return await RefreshResult(); + } + public ParallelCommands(PointInTime? result) : this(new List(), result) { @@ -19,8 +36,10 @@ public class ParallelCommands { Id = _idCounter++; - _commands = commands; - Commands = _commands.AsReadOnly(); + _commands = new SourceList(); + _commands.Edit((innerList) => innerList.AddRange(commands)); + Commands = _commands.Connect(); + CommandsCollection = Commands.ToBindedCollection(); Result = result; } @@ -63,10 +82,17 @@ public class ParallelCommands } } - public async Task RefreshResult(PointInTime? startPoint) + public async Task RemoveCommand(ICommand command) { - var result = startPoint; - foreach (var commandTimeState in _commands) + var commandTimeState = _commands.Items.First(c => c.Command == command); + _commands.Remove(commandTimeState); + await RefreshResult(); + } + + public async Task RefreshResult() + { + var result = StartTime; + foreach (var commandTimeState in _commands.Items) { await commandTimeState.UpdateStateAsync(result); if (result != null) @@ -87,7 +113,8 @@ public class ParallelCommands return Result; } - public void RemoveAt(int number) => _commands.RemoveAt(number); - - internal void Remove(CommandTimeState command) => _commands.Remove(command); + public void Dispose() + { + CommandsCollection.Dispose(); + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/CommandBase.cs b/src/Core/FileTime.Core.Command/CommandBase.cs new file mode 100644 index 0000000..f2aab1c --- /dev/null +++ b/src/Core/FileTime.Core.Command/CommandBase.cs @@ -0,0 +1,36 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using FileTime.Core.Timeline; + +namespace FileTime.Core.Command; + +public abstract class CommandBase : ICommand +{ + private readonly BehaviorSubject _displayLabel; + private readonly BehaviorSubject _totalProgress; + private readonly BehaviorSubject _currentProgress; + + public IObservable DisplayLabel { get; } + public IObservable TotalProgress { get; } + public IObservable CurrentProgress { get; } + + protected CommandBase(string displayLabel = "", int totalProgress = 0, int currentProgress = 0) + { + _displayLabel = new(displayLabel); + _totalProgress = new(totalProgress); + _currentProgress = new(currentProgress); + + DisplayLabel = _displayLabel.AsObservable(); + TotalProgress = _totalProgress.AsObservable(); + CurrentProgress = _currentProgress.AsObservable(); + } + + public abstract Task CanRun(PointInTime currentTime); + public abstract Task SimulateCommand(PointInTime currentTime); + + protected void SetDisplayLabel(string displayLabel) => _displayLabel.OnNext(displayLabel); + + protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress); + + protected void SetCurrentProgress(int currentProgress) => _currentProgress.OnNext(currentProgress); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index a6d6683..719221b 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -5,7 +5,7 @@ using FileTime.Core.Timeline; namespace FileTime.Core.Command.Copy; -public class CopyCommand : ITransportationCommand +public class CopyCommand : CommandBase, ITransportationCommand { private readonly ITimelessContentProvider _timelessContentProvider; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; @@ -22,18 +22,19 @@ public class CopyCommand : ITransportationCommand public CopyCommand( ITimelessContentProvider timelessContentProvider, ICommandSchedulerNotifier commandSchedulerNotifier) + : base("Copy") { _timelessContentProvider = timelessContentProvider; _commandSchedulerNotifier = commandSchedulerNotifier; } - public Task CanRun(PointInTime currentTime) + public override Task CanRun(PointInTime currentTime) { //TODO: return Task.FromResult(CanCommandRun.True); } - public async Task SimulateCommand(PointInTime currentTime) + public override async Task SimulateCommand(PointInTime currentTime) { if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null"); if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null"); diff --git a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs index 260f879..c8abeaa 100644 --- a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs +++ b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs @@ -7,7 +7,7 @@ using InitableService; namespace FileTime.Core.Command.Create; -public abstract class CreateItemBase : IExecutableCommand, IInitable +public abstract class CreateItemBase : CommandBase, IExecutableCommand, IInitable { private readonly ITimelessContentProvider _timelessContentProvider; private readonly IContentAccessorFactory _contentAccessorFactory; @@ -17,12 +17,13 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable CanRun(PointInTime currentTime) + public override async Task CanRun(PointInTime currentTime) { if (Parent is null) { @@ -49,7 +50,7 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable CanCommandRun.True, - { Type: AbsolutePathType.Container } => CanCommandRun.Forcable, + {Type: AbsolutePathType.Container} => CanCommandRun.Forcable, _ => CanCommandRun.False }; } @@ -60,7 +61,7 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable SimulateCommand(PointInTime currentTime) + public override Task SimulateCommand(PointInTime currentTime) { if (Parent is null) { diff --git a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs index d20572e..d78b609 100644 --- a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs @@ -5,7 +5,7 @@ using FileTime.Core.Timeline; namespace FileTime.Core.Command.Delete; -public class DeleteCommand : IExecutableCommand +public class DeleteCommand : CommandBase, IExecutableCommand { private readonly IContentAccessorFactory _contentAccessorFactory; private readonly ITimelessContentProvider _timelessContentProvider; @@ -15,18 +15,19 @@ public class DeleteCommand : IExecutableCommand public DeleteCommand( IContentAccessorFactory contentAccessorFactory, ITimelessContentProvider timelessContentProvider) + : base("Delete") { _contentAccessorFactory = contentAccessorFactory; _timelessContentProvider = timelessContentProvider; } - public Task CanRun(PointInTime currentTime) + public override Task CanRun(PointInTime currentTime) { //TODO return Task.FromResult(CanCommandRun.True); } - public Task SimulateCommand(PointInTime currentTime) + public override Task SimulateCommand(PointInTime currentTime) { //TODO return Task.FromResult(currentTime); @@ -38,8 +39,8 @@ public class DeleteCommand : IExecutableCommand //Delete await TraverseTree( - PointInTime.Present, - ItemsToDelete, + PointInTime.Present, + ItemsToDelete, new Dictionary(), new DeleteStrategy() ); @@ -69,9 +70,9 @@ public class DeleteCommand : IExecutableCommand if (itemToDelete is IContainer container) { await TraverseTree( - currentTime, - (await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty(), - itemDeleters, + currentTime, + (await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty(), + itemDeleters, deleteStrategy ); } diff --git a/src/Core/FileTime.Core.Timeline/CommandScheduler.cs b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs index bb1bc94..3d4d39b 100644 --- a/src/Core/FileTime.Core.Timeline/CommandScheduler.cs +++ b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs @@ -13,28 +13,31 @@ public class CommandScheduler : ICommandScheduler private readonly Subject _containerToRefresh = new(); private readonly object _guard = new(); - private bool _enableRunning = true; + private bool _isRunningEnabled = true; private bool _resourceIsInUse; public IObservable ContainerToRefresh { get; } - public bool EnableRunning - { - get - { - var result = true; - RunWithLock(() => result = _enableRunning); - return result; - } + public bool IsRunningEnabled => _isRunningEnabled; - set { RunWithLock(() => _enableRunning = value); } + public async Task SetRunningEnabledAsync(bool value) + { + _isRunningEnabled = value; + if (value) + { + await RunWithLockAsync(ExecuteCommands); + } } + public IObservable> CommandsToRun { get; } + public CommandScheduler(ILocalCommandExecutor localExecutor) { + CommandsToRun = _commandsToRun.Connect(); + ContainerToRefresh = _containerToRefresh.AsObservable(); - localExecutor.CommandFinished += LocalExecutorOnCommandFinished; + localExecutor.CommandFinished += ExecutorOnCommandFinished; _commandExecutors.Add(localExecutor); } @@ -46,6 +49,7 @@ public class CommandScheduler : ICommandScheduler if (_commandsToRun.Count == 0) { + //TODO: Add event handler to update batchToAdd = new ParallelCommands(PointInTime.CreateEmpty()); _commandsToRun.Add(batchToAdd); } @@ -82,13 +86,11 @@ public class CommandScheduler : ICommandScheduler private void ExecuteCommands() { - if (!_enableRunning) return; + if (!_isRunningEnabled) return; - var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault(); - if (parallelCommandsToExecute is null || - parallelCommandsToExecute.Commands.All(c => c.ExecutionState != ExecutionState.Waiting)) return; + var commandsToExecute = _commandsToRun.Items.FirstOrDefault()?.CommandsCollection.Collection; + if (commandsToExecute is null || commandsToExecute.All(c => c.ExecutionState != ExecutionState.Initializing && c.ExecutionState != ExecutionState.Waiting)) return; - var commandsToExecute = parallelCommandsToExecute.Commands; foreach (var commandToExecute in commandsToExecute) { @@ -108,15 +110,20 @@ public class CommandScheduler : ICommandScheduler return _commandExecutors[0]; } - private void LocalExecutorOnCommandFinished(object? sender, ICommand command) + private async void ExecutorOnCommandFinished(object? sender, ICommand command) { - var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault(); - if (parallelCommandsToExecute is null) return; + var firstCommandBlock = _commandsToRun + .Items + .FirstOrDefault(); + var state = firstCommandBlock + ?.CommandsCollection + .Collection + ?.FirstOrDefault(c => c.Command == command); - var state = parallelCommandsToExecute.Commands.FirstOrDefault(c => c.Command == command); if (state is null) return; state.ExecutionState = ExecutionState.Finished; + if (firstCommandBlock is not null) await firstCommandBlock.RemoveCommand(command); } private async Task RefreshCommands() @@ -125,7 +132,7 @@ public class CommandScheduler : ICommandScheduler foreach (var batch in _commandsToRun.Items) { - currentTime = await batch.RefreshResult(currentTime); + currentTime = await batch.SetStartTimeAsync(currentTime); } } diff --git a/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs b/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs index 74c298a..ea35571 100644 --- a/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs +++ b/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs @@ -5,7 +5,7 @@ namespace FileTime.Core.Timeline; public class LocalCommandExecutor : ILocalCommandExecutor { private readonly ICommandRunner _commandRunner; - public event EventHandler CommandFinished; + public event EventHandler? CommandFinished; public LocalCommandExecutor(ICommandRunner commandRunner) { @@ -15,7 +15,7 @@ public class LocalCommandExecutor : ILocalCommandExecutor public void ExecuteCommand(ICommand command) { var context = new CommandRunnerContext(command); - var thread = new Thread(new ParameterizedThreadStart(RunCommand)); + var thread = new Thread(RunCommand); thread.Start(context); } @@ -29,6 +29,6 @@ public class LocalCommandExecutor : ILocalCommandExecutor } catch (Exception ex) { } - CommandFinished.Invoke(this, context.Command); + CommandFinished?.Invoke(this, context.Command); } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs index 9d4236a..13052b2 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Configuration/MainConfiguration.cs @@ -56,6 +56,7 @@ public static class MainConfiguration //new CommandBindingConfiguration(ConfigCommand.Cut, new[] { Key.D, Key.D }), //new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)), new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemComma, shift: true)), + new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemQuestion, shift: true)), //new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }), //new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }), new(GoToHomeCommand.CommandName, new[] { Key.G, Key.H }), @@ -92,9 +93,9 @@ public static class MainConfiguration new(SwitchToTabCommand.SwitchToTab6CommandName, Key.D6), new(SwitchToTabCommand.SwitchToTab7CommandName, Key.D7), new(SwitchToTabCommand.SwitchToTab8CommandName, Key.D8), - //new CommandBindingConfiguration(ConfigCommand.TimelinePause, new[] { Key.T, Key.P }), + new (PauseCommandSchedulerCommand.CommandName, new[] { Key.T, Key.P }), //new CommandBindingConfiguration(ConfigCommand.TimelineRefresh, new[] { Key.T, Key.R }), - //new CommandBindingConfiguration(ConfigCommand.TimelineStart, new[] { Key.T, Key.S }), + new (StartCommandSchedulerCommand.CommandName, new[] { Key.T, Key.S }), //new CommandBindingConfiguration(ConfigCommand.ToggleAdvancedIcons, new[] { Key.Z, Key.I }), new(GoUpCommand.CommandName, Key.Left), new(OpenSelectedCommand.CommandName, Key.Right), diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs index 7d980ea..7643ea5 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using FileTime.App.Core.Models; using FileTime.App.Core.ViewModels; +using FileTime.Core.Models; using FileTime.GuiApp.Configuration; namespace FileTime.GuiApp.ViewModels; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs index ed11a65..0263a66 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs @@ -1,15 +1,20 @@ using System.Collections.ObjectModel; -using FileTime.App.Core.Models; using FileTime.App.Core.ViewModels; +using FileTime.App.Core.ViewModels.Timeline; +using FileTime.Core.Models; using FileTime.GuiApp.Configuration; using FileTime.GuiApp.ViewModels; using MvvmGen; namespace FileTime.GuiApp.CustomImpl.ViewModels; -[ViewModel] +[ViewModel(GenerateConstructor = false)] public partial class GuiAppState : AppStateBase, IGuiAppState { + public GuiAppState(ITimelineViewModel timelineViewModel) : base(timelineViewModel) + { + } + [Property] private bool _isAllShortcutVisible; [Property] private bool _noCommandFound; @@ -18,7 +23,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState [Property] private BindedCollection _rootDriveInfos = new(); - [Property] private IReadOnlyList _places; + [Property] private IReadOnlyList _places = new List(); public List PreviousKeys { get; } = new(); public ObservableCollection PopupTexts { get; } = new(); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml index f3d2e0a..6b7ff84 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml @@ -1,9 +1,10 @@ - + - + @@ -28,7 +29,7 @@ - + + \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs index 4af8633..c591c9a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs @@ -4,6 +4,7 @@ using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; +using FileTime.Core.Extensions; using FileTime.Core.Models; using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Extensions; @@ -43,8 +44,8 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler _appState.SelectedTab.Subscribe(t => _selectedTab = t); _appState.SelectedTab.Select(t => t == null ? Observable.Return(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l); - - _openModals = new BindedCollection(modalService.OpenModals); + + _openModals = modalService.OpenModals.ToBindedCollection(); _keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) }); _keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) }); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RapidTravelModeKeyInputHandler.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RapidTravelModeKeyInputHandler.cs index ee33bcf..81ba155 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RapidTravelModeKeyInputHandler.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RapidTravelModeKeyInputHandler.cs @@ -3,6 +3,7 @@ using FileTime.App.Core.Models; using FileTime.App.Core.Services; using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; +using FileTime.Core.Extensions; using FileTime.Core.Models; using FileTime.Core.Services; using FileTime.GuiApp.Configuration; @@ -42,7 +43,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler _appState.SelectedTab.Subscribe(t => _selectedTab = t); - _openModals = new BindedCollection(modalService.OpenModals); + _openModals = modalService.OpenModals.ToBindedCollection(); } public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action setHandled) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs index ea0e780..d184be5 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/RootDriveInfoService.cs @@ -4,6 +4,7 @@ using DynamicData; using DynamicData.Binding; using FileTime.App.Core.Models; using FileTime.App.Core.Services; +using FileTime.Core.Extensions; using FileTime.Core.Models; using FileTime.Core.Timeline; using FileTime.GuiApp.ViewModels; @@ -16,7 +17,7 @@ public class RootDriveInfoService : IStartupHandler private readonly SourceList _rootDrives = new(); public RootDriveInfoService( - IGuiAppState guiAppState, + IGuiAppState guiAppState, ILocalContentProvider localContentProvider, ITimelessContentProvider timelessContentProvider) { @@ -53,7 +54,7 @@ public class RootDriveInfoService : IStartupHandler .Transform(t => new RootDriveInfo(t.Drive, t.Container)) .Sort(SortExpressionComparer.Ascending(d => d.Name)); - guiAppState.RootDriveInfos = new BindedCollection(rootDriveInfos); + guiAppState.RootDriveInfos = rootDriveInfos.ToBindedCollection(); void InitRootDrives() { diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 7a0ec41..b1f7701 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -2,35 +2,35 @@ x:Class="FileTime.GuiApp.Views.MainWindow" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:appCoreModels="using:FileTime.App.Core.Models" + xmlns:config="using:FileTime.GuiApp.Configuration" xmlns:corevm="using:FileTime.App.Core.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:interactions="using:FileTime.Core.Interactions" xmlns:local="using:FileTime.GuiApp.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:FileTime.GuiApp.ViewModels" - xmlns:config="using:FileTime.GuiApp.Configuration" - xmlns:appCoreModels="using:FileTime.App.Core.Models" - xmlns:interactions="using:FileTime.Core.Interactions" - MinHeight="600" - MinWidth="800" Title="FileTime" + MinWidth="800" + MinHeight="600" d:DesignHeight="450" d:DesignWidth="800" x:CompileBindings="True" x:DataType="vm:IMainWindowViewModelBase" Background="Transparent" + Closed="OnWindowClosed" ExtendClientAreaToDecorationsHint="True" Icon="/Assets/filetime.ico" InputElement.KeyDown="OnKeyDown" Opened="OnWindowOpened" - Closed="OnWindowClosed" TransparencyLevelHint="Blur" mc:Ignorable="d"> - - - + + + @@ -62,18 +62,22 @@ - + - + - + - - + + + IsVisible="{Binding Label, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" + Text="{Binding Label}" /> + HorizontalAlignment="Right" + VerticalAlignment="Center" + Orientation="Horizontal"> - - + - - + - - + @@ -152,8 +163,12 @@ - + - - + + + Source="{Binding Container, Converter={StaticResource ItemToImageConverter}}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -308,9 +367,9 @@ + HorizontalContentAlignment="Stretch" + ShowAttributes="False" /> @@ -326,8 +385,7 @@ - + + ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem^}"> - - + + - - - - + + + + - +