Console DialogService

This commit is contained in:
2023-08-14 11:50:59 +02:00
parent 6797c26bf9
commit 1f4b938358
41 changed files with 807 additions and 269 deletions

View File

@@ -6,11 +6,18 @@ using FileTime.ConsoleUI.App.KeyInputHandling;
using GeneralInputKey;
using TerminalUI;
using TerminalUI.ConsoleDrivers;
using TerminalUI.Traits;
namespace FileTime.ConsoleUI.App;
public class App : IApplication
{
private static readonly List<Keys> KeysToFurtherProcess = new()
{
Keys.Enter,
Keys.Escape
};
private readonly ILifecycleService _lifecycleService;
private readonly IConsoleAppState _consoleAppState;
@@ -86,11 +93,13 @@ public class App : IApplication
SpecialKeysStatus = specialKeysStatus
};
if (focusManager.Focused is { } focused)
var focused = focusManager.Focused;
if (focused is { })
{
focused.HandleKeyInput(keyEventArgs);
}
else
if (focused is null || (!keyEventArgs.Handled && KeysToFurtherProcess.Contains(keyEventArgs.Key)))
{
_keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus);
}

View File

@@ -1,16 +0,0 @@
using FileTime.Core.Interactions;
namespace FileTime.ConsoleUI.App;
public class ConsoleUserCommunicationService : IUserCommunicationService
{
public Task<bool> ReadInputs(params IInputElement[] fields) => throw new NotImplementedException();
public Task<bool> ReadInputs(IInputElement field, IEnumerable<IPreviewElement>? previews = null) => throw new NotImplementedException();
public Task<bool> ReadInputs(IEnumerable<IInputElement> fields, IEnumerable<IPreviewElement>? previews = null) => throw new NotImplementedException();
public void ShowToastMessage(string text) => throw new NotImplementedException();
public Task<MessageBoxResult> ShowMessageBox(string text, bool showCancel = true, string? okText = null, string? cancelText = null) => throw new NotImplementedException();
}

View File

@@ -65,7 +65,7 @@ public class CommandPalette
{
new Border<IRootViewModel>
{
Margin = new Thickness(0, 0, 0, 2),
Margin = new Thickness(0, 0, 0, 1),
Content = inputTextBox
},
new ListView<IRootViewModel, ICommandPaletteEntryViewModel>
@@ -103,15 +103,13 @@ public class CommandPalette
item.Bind(
item.Parent,
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
t => t.Background,
v => v
t => t.Background
);
item.Bind(
item.Parent,
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
t => t.Foreground,
v => v
t => t.Foreground
);
return root;

View File

@@ -28,11 +28,11 @@ public class KeyInputHandlerService : IKeyInputHandlerService
if (_appState.ViewMode.Value == ViewMode.Default)
{
Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent)).Wait();
Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent));
}
else
{
Task.Run(async () => await _rapidTravelModeKeyInputHandler.HandleInputKey(keyEvent)).Wait();
Task.Run(async () => await _rapidTravelModeKeyInputHandler.HandleInputKey(keyEvent));
}
}
}

View File

@@ -1,13 +1,18 @@
using FileTime.App.Core.Models.Enums;
using System.Collections.Specialized;
using System.ComponentModel;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Controls;
using FileTime.ConsoleUI.App.Styling;
using FileTime.Core.Enums;
using FileTime.Core.Interactions;
using GeneralInputKey;
using TerminalUI;
using TerminalUI.Color;
using TerminalUI.Controls;
using TerminalUI.Extensions;
using TerminalUI.Models;
using TerminalUI.Traits;
using TerminalUI.ViewExtensions;
namespace FileTime.ConsoleUI.App;
@@ -18,9 +23,12 @@ public class MainWindow
private readonly IApplicationContext _applicationContext;
private readonly ITheme _theme;
private readonly CommandPalette _commandPalette;
private readonly Lazy<IView> _root;
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
private IInputElement? _inputElementToFocus;
private Action? _readInputChildHandlerUnsubscriber;
public MainWindow(
IRootViewModel rootViewModel,
IApplicationContext applicationContext,
@@ -32,6 +40,40 @@ public class MainWindow
_theme = theme;
_commandPalette = commandPalette;
_root = new Lazy<IView>(Initialize);
rootViewModel.FocusReadInputElement += element =>
{
_inputElementToFocus = element;
UpdateReadInputsFocus();
};
}
private void UpdateReadInputsFocus()
{
foreach (var readInputsChild in _readInputs.Children)
{
if (readInputsChild.DataContext == _inputElementToFocus)
{
if (FindFocusable(readInputsChild) is { } focusable)
{
focusable.Focus();
_inputElementToFocus = null;
break;
}
}
}
IFocusable? FindFocusable(IView view)
{
if (view is IFocusable focusable) return focusable;
foreach (var viewVisualChild in view.VisualChildren)
{
if (FindFocusable(viewVisualChild) is { } focusableChild)
return focusableChild;
}
return null;
}
}
public IEnumerable<IView> RootViews() => new[]
@@ -46,10 +88,33 @@ public class MainWindow
Name = "root",
DataContext = _rootViewModel,
ApplicationContext = _applicationContext,
Foreground = _theme.DefaultForegroundColor,
ChildInitializer =
{
MainContent(),
_commandPalette.View()
_commandPalette.View(),
Dialogs(),
}
};
((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(ItemsControl<object, object>.Children))
{
_readInputChildHandlerUnsubscriber?.Invoke();
UpdateReadInputsFocus();
if (_readInputs.Children is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += NotifyCollectionChangedEventHandler;
_readInputChildHandlerUnsubscriber = () => { notifyCollectionChanged.CollectionChanged -= NotifyCollectionChangedEventHandler; };
}
void NotifyCollectionChangedEventHandler(
object? sender,
NotifyCollectionChangedEventArgs e)
{
UpdateReadInputsFocus();
}
}
};
return root;
@@ -188,7 +253,6 @@ public class MainWindow
ItemTemplate = item =>
{
var textBlock = item.CreateChild<TextBlock<ITabViewModel>>();
textBlock.Foreground = _theme.DefaultForegroundColor;
textBlock.Bind(
textBlock,
@@ -356,4 +420,137 @@ public class MainWindow
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor,
_ => throw new NotImplementedException()
};
private IView<IRootViewModel> Dialogs()
{
var root = new Border<IRootViewModel>()
{
Margin = 5,
BorderThickness = 1,
Content = new Grid<IRootViewModel>()
{
ChildInitializer =
{
ReadInputs()
}
}
};
root.Bind(
root,
d => d.DialogService.ReadInput.Value != null,
v => v.IsVisible);
return root;
}
private ItemsControl<IRootViewModel, IInputElement> ReadInputs()
{
var readInputs = new ItemsControl<IRootViewModel, IInputElement>
{
ItemTemplate = () =>
{
var root = new Grid<IInputElement>
{
ColumnDefinitionsObject = "* *",
ChildInitializer =
{
new TextBlock<IInputElement>()
.Setup(t => t.Bind(
t,
c => c.Label,
tb => tb.Text
)),
new Grid<IInputElement>()
{
Extensions =
{
new GridPositionExtension(1, 0)
},
ChildInitializer =
{
new Border<IInputElement>
{
Content =
new TextBox<IInputElement>()
.Setup(t => t.Bind(
t,
d => ((TextInputElement) d).Value,
tb => tb.Text,
v => v ?? string.Empty,
fallbackValue: string.Empty
))
.WithTextHandler((tb, t) =>
{
if (tb.DataContext is TextInputElement textInputElement)
textInputElement.Value = t;
})
}
.Setup(t => t.Bind(
t,
d => d.Type == InputType.Text,
tb => tb.IsVisible
)),
new Border<IInputElement>
{
Content =
new TextBox<IInputElement>
{
PasswordChar = '*'
}
.Setup(t => t.Bind(
t,
d => ((PasswordInputElement) d).Value,
tb => tb.Text,
v => v ?? string.Empty,
fallbackValue: string.Empty
))
.WithTextHandler((tb, t) =>
{
if (tb.DataContext is PasswordInputElement textInputElement)
textInputElement.Value = t;
})
}
.Setup(t => t.Bind(
t,
d => d.Type == InputType.Password,
tb => tb.IsVisible
))
//TODO: OptionInputElement
}
}
}
};
return root;
}
}
.Setup(t => t.Bind(
t,
d => d.DialogService.ReadInput.Value.Inputs,
c => c.ItemsSource,
v => v
));
readInputs.WithKeyHandler((_, e) =>
{
if (e.Key == Keys.Enter)
{
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
readInputsViewModel.Process();
e.Handled = true;
}
else if (e.Key == Keys.Escape)
{
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
readInputsViewModel.Cancel();
e.Handled = true;
}
});
_readInputs = readInputs;
return readInputs;
}
}

View File

@@ -1,5 +1,7 @@
using FileTime.App.CommandPalette.ViewModels;
using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Services;
using FileTime.Core.Interactions;
namespace FileTime.ConsoleUI.App;
@@ -10,14 +12,29 @@ public class RootViewModel : IRootViewModel
public IPossibleCommandsViewModel PossibleCommands { get; }
public IConsoleAppState AppState { get; }
public ICommandPaletteViewModel CommandPalette { get; }
public IDialogService DialogService { get; }
public event Action<IInputElement>? FocusReadInputElement;
public RootViewModel(
IConsoleAppState appState,
IPossibleCommandsViewModel possibleCommands,
ICommandPaletteViewModel commandPalette)
ICommandPaletteViewModel commandPalette,
IDialogService dialogService)
{
AppState = appState;
PossibleCommands = possibleCommands;
CommandPalette = commandPalette;
DialogService = dialogService;
DialogService.ReadInput.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(DialogService.ReadInput.Value))
{
if (DialogService.ReadInput.Value is {Inputs.Count: > 0} readInputs)
{
FocusReadInputElement?.Invoke(readInputs.Inputs[0]);
}
}
};
}
}

View File

@@ -0,0 +1,15 @@
using FileTime.App.Core.Services;
namespace FileTime.ConsoleUI.App.Services;
public class DialogService : DialogServiceBase, IDialogService
{
public DialogService(IModalService modalService) : base(modalService)
{
}
public override void ShowToastMessage(string text)
{
// TODO: Implement
}
}

View File

@@ -19,13 +19,14 @@ public static class Startup
services.TryAddSingleton<IApplication, App>();
services.TryAddSingleton<IConsoleAppState, ConsoleAppState>();
services.TryAddSingleton<IAppState>(sp => sp.GetRequiredService<IConsoleAppState>());
services.TryAddSingleton<IUserCommunicationService, ConsoleUserCommunicationService>();
services.TryAddSingleton<IKeyInputHandlerService, KeyInputHandlerService>();
services.TryAddSingleton<IAppKeyService<ConsoleKey>, ConsoleAppKeyService>();
services.TryAddSingleton<ISystemClipboardService, ConsoleSystemClipboardService>();
services.AddSingleton<CustomLoggerSink>();
services.TryAddSingleton(new ApplicationConfiguration(true));
services.TryAddSingleton<IRootViewModel, RootViewModel>();
services.TryAddSingleton<IDialogService, DialogService>();
services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>());
services.Configure<ConsoleApplicationConfiguration>(configuration);
return services;