Console DialogService
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user