Preview refactor, Console rename form

This commit is contained in:
2023-08-14 16:42:22 +02:00
parent 2a595b2548
commit 8aa8d83598
25 changed files with 610 additions and 348 deletions

View File

@@ -1,12 +1,11 @@
using System.Collections.Specialized;
using FileTime.App.Core.Models;
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.KeyInputHandling;
using GeneralInputKey;
using Microsoft.Extensions.Logging;
using TerminalUI;
using TerminalUI.ConsoleDrivers;
using TerminalUI.Traits;
namespace FileTime.ConsoleUI.App;
@@ -27,6 +26,7 @@ public class App : IApplication
private readonly IApplicationContext _applicationContext;
private readonly IConsoleDriver _consoleDriver;
private readonly IAppState _appState;
private readonly ILogger<App> _logger;
private readonly IKeyInputHandlerService _keyInputHandlerService;
private readonly Thread _renderThread;
@@ -38,7 +38,8 @@ public class App : IApplication
MainWindow mainWindow,
IApplicationContext applicationContext,
IConsoleDriver consoleDriver,
IAppState appState)
IAppState appState,
ILogger<App> logger)
{
_lifecycleService = lifecycleService;
_keyInputHandlerService = keyInputHandlerService;
@@ -48,6 +49,7 @@ public class App : IApplication
_applicationContext = applicationContext;
_consoleDriver = consoleDriver;
_appState = appState;
_logger = logger;
_renderThread = new Thread(Render);
}
@@ -74,12 +76,13 @@ public class App : IApplication
while (_applicationContext.IsRunning)
{
if (_consoleDriver.CanRead())
try
{
var key = _consoleDriver.ReadKey();
if (_appKeyService.MapKey(key.Key) is { } mappedKey)
if (_consoleDriver.CanRead())
{
var key = _consoleDriver.ReadKey();
var mappedKey = _appKeyService.MapKey(key.Key);
SpecialKeysStatus specialKeysStatus = new(
(key.Modifiers & ConsoleModifiers.Alt) != 0,
(key.Modifiers & ConsoleModifiers.Shift) != 0,
@@ -100,12 +103,16 @@ public class App : IApplication
_applicationContext.FocusManager.HandleKeyInput(keyEventArgs);
}
if (focused is null || (!keyEventArgs.Handled && KeysToFurtherProcess.Contains(keyEventArgs.Key)))
if (focused is null || (keyEventArgs is {Handled: false, Key: { } k} && KeysToFurtherProcess.Contains(k)))
{
_keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus);
}
}
}
catch (Exception e)
{
_logger.LogError(e, "Error while handling key input");
}
Thread.Sleep(10);
}

View File

@@ -0,0 +1,356 @@
using System.Collections.Specialized;
using System.ComponentModel;
using FileTime.App.Core.Interactions;
using FileTime.ConsoleUI.App.Styling;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
using GeneralInputKey;
using TerminalUI.Controls;
using TerminalUI.Extensions;
using TerminalUI.Models;
using TerminalUI.Traits;
using TerminalUI.ViewExtensions;
namespace FileTime.ConsoleUI.App.Controls;
public class Dialogs
{
private readonly IRootViewModel _rootViewModel;
private readonly ITheme _theme;
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
private IInputElement? _inputElementToFocus;
private Action? _readInputChildHandlerUnSubscriber;
public Dialogs(IRootViewModel rootViewModel, ITheme theme)
{
_rootViewModel = rootViewModel;
_theme = theme;
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 IView<IRootViewModel> View()
{
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);
((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(ItemsControl<object, object>.Children))
{
_readInputChildHandlerUnSubscriber?.Invoke();
if (_readInputs.Children.Count > 0)
{
UpdateReadInputsFocus();
}
else
{
_inputElementToFocus = null;
}
if (_readInputs.Children is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += NotifyCollectionChangedEventHandler;
_readInputChildHandlerUnSubscriber = () => { notifyCollectionChanged.CollectionChanged -= NotifyCollectionChangedEventHandler; };
}
void NotifyCollectionChangedEventHandler(
object? sender,
NotifyCollectionChangedEventArgs e)
{
UpdateReadInputsFocus();
}
}
};
return root;
}
private IView<IRootViewModel> ReadInputs()
=> new Grid<IRootViewModel>
{
RowDefinitionsObject = "Auto Auto",
ChildInitializer =
{
ReadInputsList(),
new ItemsControl<IRootViewModel, IPreviewElement>
{
ItemTemplate = ReadInputPreviewItemTemplate
}
.Setup(i => i.Bind(
i,
dc => dc.DialogService.ReadInput.Value.Previews,
c => c.ItemsSource
))
.WithExtension(new GridPositionExtension(0, 1))
}
};
private IView<IPreviewElement> ReadInputPreviewItemTemplate()
{
var grid = new Grid<IPreviewElement>
{
ChildInitializer =
{
new ItemsControl<IPreviewElement, IPreviewElement>
{
ItemTemplate = ReadInputPreviewItemTemplate
}
.Setup(i => i.Bind(
i,
dc => (PreviewType) dc.PreviewType == PreviewType.PreviewList,
c => c.IsVisible))
.Setup(i => i.Bind(
i,
dc => ((PreviewList) dc).Items,
c => c.ItemsSource)),
new Grid<IPreviewElement>
{
ColumnDefinitionsObject = "* *",
ChildInitializer =
{
new TextBlock<IPreviewElement>()
.Setup(t => t.Bind(
t,
dc => ((DoubleTextPreview) dc).Text1,
tb => tb.Text
)),
new TextBlock<IPreviewElement>
{
Extensions =
{
new GridPositionExtension(1, 0)
}
}
.Setup(t => t.Bind(
t,
dc => ((DoubleTextPreview) dc).Text2,
tb => tb.Text
))
}
}.Setup(g => g.Bind(
g,
dc => (PreviewType) dc.PreviewType == PreviewType.DoubleText,
g => g.IsVisible)),
new Grid<IPreviewElement>
{
ColumnDefinitionsObject = "* *",
ChildInitializer =
{
new ItemsControl<IPreviewElement, ItemNamePart>()
{
Orientation = Orientation.Horizontal,
ItemTemplate = ItemNamePartItemTemplate
}.Setup(i => i.Bind(
i,
dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts1,
c => c.ItemsSource,
v => v)),
new ItemsControl<IPreviewElement, ItemNamePart>()
{
Orientation = Orientation.Horizontal,
Extensions =
{
new GridPositionExtension(1, 0)
},
ItemTemplate = ItemNamePartItemTemplate
}.Setup(i => i.Bind(
i,
dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts2,
c => c.ItemsSource))
}
}.Setup(g => g.Bind(
g,
dc => (PreviewType) dc.PreviewType == PreviewType.DoubleItemNamePartList,
g => g.IsVisible))
}
};
return grid;
IView<ItemNamePart> ItemNamePartItemTemplate()
{
var textBlock = new TextBlock<ItemNamePart>();
textBlock.Bind(
textBlock,
dc => dc.Text,
tb => tb.Text
);
textBlock.Bind(
textBlock,
dc => dc.IsSpecial ? _theme.DefaultForegroundAccentColor : null,
tb => tb.Foreground
);
return textBlock;
}
}
private IView<IRootViewModel> ReadInputsList()
{
var readInputs = new ItemsControl<IRootViewModel, IInputElement>
{
IsFocusBoundary = true,
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
))
.Setup(t => t.Bind(
t,
d => ((TextInputElement) d).Label,
tb => tb.Name))
.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
))
.Setup(t => t.Bind(
t,
d => ((PasswordInputElement) d).Label,
tb => tb.Name))
.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

@@ -6,7 +6,6 @@ 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;
@@ -23,57 +22,23 @@ public class MainWindow
private readonly IApplicationContext _applicationContext;
private readonly ITheme _theme;
private readonly CommandPalette _commandPalette;
private readonly Dialogs _dialogs;
private readonly Lazy<IView> _root;
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
private IInputElement? _inputElementToFocus;
private Action? _readInputChildHandlerUnsubscriber;
public MainWindow(
IRootViewModel rootViewModel,
IApplicationContext applicationContext,
ITheme theme,
CommandPalette commandPalette)
CommandPalette commandPalette,
Dialogs dialogs)
{
_rootViewModel = rootViewModel;
_applicationContext = applicationContext;
_theme = theme;
_commandPalette = commandPalette;
_dialogs = dialogs;
_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[]
@@ -93,28 +58,7 @@ public class MainWindow
{
MainContent(),
_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();
}
_dialogs.View(),
}
};
return root;
@@ -441,146 +385,4 @@ 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>
{
IsFocusBoundary = true,
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
))
.Setup(t => t.Bind(
t,
d => ((TextInputElement) d).Label,
tb => tb.Name))
.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
))
.Setup(t => t.Bind(
t,
d => ((PasswordInputElement) d).Label,
tb => tb.Name))
.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

@@ -36,6 +36,7 @@ public static class Startup
{
services.TryAddSingleton<MainWindow>();
services.TryAddSingleton<CommandPalette>();
services.TryAddSingleton<Dialogs>();
return services;
}
}