From bc865011d3cdacebb3f7d5c79816751ab92b0479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Mon, 28 Aug 2023 13:49:54 +0200 Subject: [PATCH] Console MessageBox, admin mode --- .../ConsoleApplicationConfiguration.cs | 1 + ...FileTime.ConsoleUI.App.Abstractions.csproj | 1 + .../IRootViewModel.cs | 2 + src/ConsoleApp/FileTime.ConsoleUI.App/App.cs | 9 + .../Controls/Dialogs.cs | 225 ++++++++++++------ .../FileTime.ConsoleUI.App/MainWindow.cs | 14 +- .../FileTime.ConsoleUI.App/RootViewModel.cs | 6 +- .../Services/CustomLoggerSink.cs | 22 +- src/ConsoleApp/FileTime.ConsoleUI/Program.cs | 4 + .../FileTime.ConsoleUI/appsettings.json | 1 + .../Avalonia/FileTime.GuiApp/Program.cs | 6 +- .../TerminalUI/ConsoleDrivers/DotnetDriver.cs | 1 + .../ConsoleDrivers/IConsoleDriver.cs | 1 + src/Library/TerminalUI/Controls/Button.cs | 94 ++++++++ .../TerminalUI/Controls/ItemsControl.cs | 3 +- src/Library/TerminalUI/EventLoop.cs | 6 + src/Library/TerminalUI/IEventLoop.cs | 1 + src/Library/TerminalUI/RenderEngine.cs | 4 + 18 files changed, 313 insertions(+), 88 deletions(-) create mode 100644 src/Library/TerminalUI/Controls/Button.cs diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs index efbec09..1029c2d 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs @@ -4,6 +4,7 @@ public class ConsoleApplicationConfiguration { public string? ConsoleDriver { get; set; } public bool DisableUtf8 { get; set; } + public string? AdminModeIcon { get; set; } public string? ClipboardSingleIcon { get; set; } public string? ClipboardMultipleIcon { get; set; } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/FileTime.ConsoleUI.App.Abstractions.csproj b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/FileTime.ConsoleUI.App.Abstractions.csproj index 1ed42e5..8a23bd8 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/FileTime.ConsoleUI.App.Abstractions.csproj +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/FileTime.ConsoleUI.App.Abstractions.csproj @@ -12,6 +12,7 @@ + diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs index 57c8b27..cc387f3 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs @@ -7,6 +7,7 @@ using FileTime.App.FrequencyNavigation.ViewModels; using FileTime.ConsoleUI.App.Services; using FileTime.Core.Interactions; using FileTime.Core.Models; +using FileTime.Providers.LocalAdmin; namespace FileTime.ConsoleUI.App; @@ -23,5 +24,6 @@ public interface IRootViewModel IFrequencyNavigationViewModel FrequencyNavigation { get; } IItemPreviewService ItemPreviewService { get; } IClipboardService ClipboardService { get; } + IAdminElevationManager AdminElevationManager { get; } event Action? FocusReadInputElement; } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs index 9109557..27642b2 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using TerminalUI; using TerminalUI.ConsoleDrivers; +using TerminalUI.Models; namespace FileTime.ConsoleUI.App; @@ -138,6 +139,14 @@ public class App : IApplication Thread.Sleep(10); } + + _consoleDriver.ExitRestrictedMode(); + _consoleDriver.Clear(); + var size = _consoleDriver.GetWindowSize(); + var shutdownText = "Shutting down..."; + + _consoleDriver.SetCursorPosition(new Position(size.Width / 2 - shutdownText.Length / 2, size.Height / 2)); + _consoleDriver.Write(shutdownText); Task.Run(async () => await _lifecycleService.ExitAsync()).Wait(); } diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs index aae63c1..ac3bbd2 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs @@ -51,35 +51,96 @@ public class Dialogs }; } - private void UpdateReadInputsFocus() + public IView View() { - foreach (var readInputsChild in _readInputs.Children) + var root = new Grid() { - if (readInputsChild.DataContext == _inputElementToFocus) + Margin = 5, + ChildInitializer = { - if (FindFocusable(readInputsChild) is { } focusable) - { - focusable.Focus(); - _inputElementToFocus = null; - break; - } + ReadInputs(), + MessageBox() } - } + }; - 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; - } + return root; } - public IView View() + private IView MessageBox() + { + var okButton = new Button + { + Margin = "0 0 5 0", + Content = new TextBlock() + .Setup(t => t.Bind( + t, + dc => dc.DialogService.LastMessageBox.Value.OkText, + t => t.Text)), + }.WithClickHandler(b => b.DataContext?.DialogService.LastMessageBox.Value?.Ok()); + + var cancelButton = + new Button + { + Margin = "0 0 5 0", + Content = new TextBlock() + .Setup(t => t.Bind( + t, + dc => dc.DialogService.LastMessageBox.Value.CancelText, + t => t.Text)), + } + .Setup(b => b.Bind( + b, + dc => dc.DialogService.LastMessageBox.Value.ShowCancel, + b => b.IsVisible)) + .WithClickHandler(b => b.DataContext?.DialogService.LastMessageBox.Value?.Cancel()); + + var root = new Border + { + Margin = 5, + BorderThickness = 1, + Background = SpecialColor.None, + Content = new Grid + { + RowDefinitionsObject = "Auto Auto", + ChildInitializer = + { + new TextBlock() + .Setup(t => t.Bind( + t, + dc => dc.DialogService.LastMessageBox.Value.Text, + t => t.Text)), + new StackPanel + { + Orientation = Orientation.Horizontal, + Extensions = {new GridPositionExtension(0, 1)}, + ChildInitializer = + { + okButton, + cancelButton + } + } + } + } + }; + + root.Bind( + root, + d => d.DialogService.LastMessageBox.Value != null, + v => v.IsVisible, + fallbackValue: false); + + ((INotifyPropertyChanged) root).PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(IView.IsVisible)) + { + okButton.Focus(); + } + }; + + return root; + } + + private IView ReadInputs() { var root = new Border { @@ -88,9 +149,20 @@ public class Dialogs Background = SpecialColor.None, Content = new Grid { + RowDefinitionsObject = "Auto Auto", ChildInitializer = { - ReadInputs() + ReadInputsList(), + new ItemsControl + { + ItemTemplate = ReadInputPreviewItemTemplate + } + .Setup(i => i.Bind( + i, + dc => dc.DialogService.ReadInput.Value.Previews, + c => c.ItemsSource + )) + .WithExtension(new GridPositionExtension(0, 1)) } } }; @@ -100,58 +172,9 @@ public class Dialogs d => d.DialogService.ReadInput.Value != null, v => v.IsVisible); - ((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) => - { - if (e.PropertyName == nameof(ItemsControl.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 ReadInputs() - => new Grid - { - RowDefinitionsObject = "Auto Auto", - ChildInitializer = - { - ReadInputsList(), - new ItemsControl - { - ItemTemplate = ReadInputPreviewItemTemplate - } - .Setup(i => i.Bind( - i, - dc => dc.DialogService.ReadInput.Value.Previews, - c => c.ItemsSource - )) - .WithExtension(new GridPositionExtension(0, 1)) - } - }; - private IView ReadInputPreviewItemTemplate() { var grid = new Grid @@ -367,6 +390,64 @@ public class Dialogs _readInputs = readInputs; + ((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(ItemsControl.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 readInputs; } + + 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; + } + } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs index c2c0ac6..3d545bb 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs @@ -138,12 +138,22 @@ public class MainWindow { Margin = "2 0 0 0", Extensions = {new GridPositionExtension(2, 0)}, + Orientation = Orientation.Horizontal, ChildInitializer = { new TextBlock + { + Text = _consoleApplicationConfiguration.Value.AdminModeIcon ?? + (_consoleApplicationConfiguration.Value.DisableUtf8 ? "A+ " : "\ud83d\udd11"), + AsciiOnly = false + }.Setup(t => t.Bind( + t, + dc => dc.AdminElevationManager.IsAdminInstanceRunning, + t => t.IsVisible)), + new TextBlock { Text = _consoleApplicationConfiguration.Value.ClipboardSingleIcon ?? - (_consoleApplicationConfiguration.Value.DisableUtf8 ? "C" : "\ud83d\udccb"), + (_consoleApplicationConfiguration.Value.DisableUtf8 ? "C " : "\ud83d\udccb"), AsciiOnly = false }.Setup(t => t.Bind( t, @@ -152,7 +162,7 @@ public class MainWindow new TextBlock { Text = _consoleApplicationConfiguration.Value.ClipboardMultipleIcon ?? - (_consoleApplicationConfiguration.Value.DisableUtf8 ? "CC" : "\ud83d\udccb+"), + (_consoleApplicationConfiguration.Value.DisableUtf8 ? "CC " : "\ud83d\udccb+"), AsciiOnly = false }.Setup(t => t.Bind( t, diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs index 8d210b9..67cc4a9 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs @@ -7,6 +7,7 @@ using FileTime.App.FrequencyNavigation.ViewModels; using FileTime.ConsoleUI.App.Services; using FileTime.Core.Interactions; using FileTime.Core.Models; +using FileTime.Providers.LocalAdmin; namespace FileTime.ConsoleUI.App; @@ -20,6 +21,7 @@ public partial class RootViewModel : IRootViewModel public IFrequencyNavigationViewModel FrequencyNavigation { get; } public IItemPreviewService ItemPreviewService { get; } public IClipboardService ClipboardService { get; } + public IAdminElevationManager AdminElevationManager { get; } public IDialogService DialogService { get; } public ITimelineViewModel TimelineViewModel { get; } public IDeclarativeProperty VolumeSizeInfo { get;} @@ -34,7 +36,8 @@ public partial class RootViewModel : IRootViewModel ITimelineViewModel timelineViewModel, IFrequencyNavigationViewModel frequencyNavigation, IItemPreviewService itemPreviewService, - IClipboardService clipboardService) + IClipboardService clipboardService, + IAdminElevationManager adminElevationManager) { AppState = appState; PossibleCommands = possibleCommands; @@ -44,6 +47,7 @@ public partial class RootViewModel : IRootViewModel FrequencyNavigation = frequencyNavigation; ItemPreviewService = itemPreviewService; ClipboardService = clipboardService; + AdminElevationManager = adminElevationManager; DialogService.ReadInput.PropertyChanged += (o, e) => { diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Services/CustomLoggerSink.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Services/CustomLoggerSink.cs index ff11885..97e2f8d 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Services/CustomLoggerSink.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Services/CustomLoggerSink.cs @@ -8,21 +8,27 @@ namespace FileTime.ConsoleUI.App.Services; public class CustomLoggerSink : ILogEventSink { private readonly Lazy _dialogService; - + public CustomLoggerSink(IServiceProvider serviceProvider) { _dialogService = new Lazy(() => serviceProvider.GetRequiredService()); } - + public void Emit(LogEvent logEvent) { - if (logEvent.Level >= LogEventLevel.Error) + if (logEvent.Level >= LogEventLevel.Error + && logEvent.Properties.TryGetValue("SourceContext", out var sourceContext)) { - var message = logEvent.RenderMessage(); - if (logEvent.Exception is not null) - message += $" {logEvent.Exception.Message}"; - Debug.WriteLine(message); - _dialogService.Value.ShowToastMessage(message); + var s = sourceContext.ToString(); + + if (s != "\"Microsoft.AspNetCore.SignalR.Client.HubConnection\"") + { + var message = logEvent.RenderMessage(); + if (logEvent.Exception is not null) + message += $" {logEvent.Exception.Message}"; + Debug.WriteLine(message); + _dialogService.Value.ShowToastMessage(message); + } } } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs index 2d3bfe6..268c245 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; using FileTime.App.Core; using FileTime.App.Core.Configuration; using FileTime.ConsoleUI; @@ -19,6 +21,7 @@ Console.OutputEncoding = System.Text.Encoding.UTF8; IConsoleDriver? driver = null; #if DEBUG + (AppDataRoot, EnvironmentName) = Init.InitDevelopment(); #endif if (AppDataRoot is null) @@ -49,6 +52,7 @@ try } finally { + driver?.Clear(); driver?.SetCursorVisible(true); driver?.Dispose(); } diff --git a/src/ConsoleApp/FileTime.ConsoleUI/appsettings.json b/src/ConsoleApp/FileTime.ConsoleUI/appsettings.json index 0f45925..b8e29f6 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/appsettings.json +++ b/src/ConsoleApp/FileTime.ConsoleUI/appsettings.json @@ -1,4 +1,5 @@ { + "AdminModeIcon": "\uf0e7", "ClipboardSingleIcon": "\udb80\udd4c", "ClipboardMultipleIcon": "\udb84\ude68" } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Program.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Program.cs index ed83569..59786f4 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Program.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Program.cs @@ -57,9 +57,9 @@ public static class Program Log.Logger.Information("Early app starting..."); - AppDomain.CurrentDomain.FirstChanceException -= OnFirstChanceException; - AppDomain.CurrentDomain.UnhandledException -= OnAppDomainUnhandledException; - TaskScheduler.UnobservedTaskException -= OnTaskSchedulerUnobservedTaskException; + AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException; + AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException; + TaskScheduler.UnobservedTaskException += OnTaskSchedulerUnobservedTaskException; try { BuildAvaloniaApp() diff --git a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs index 1e37b67..b8395c9 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs @@ -11,6 +11,7 @@ public class DotnetDriver : IConsoleDriver public int ThreadId { get; set; } public void EnterRestrictedMode() => CheckThreadId = true; + public void ExitRestrictedMode() => CheckThreadId = false; public virtual bool Init() { diff --git a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs index 72c8bad..93b085d 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs @@ -24,4 +24,5 @@ public interface IConsoleDriver Size GetWindowSize(); void Clear(); void EnterRestrictedMode(); + void ExitRestrictedMode(); } \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/Button.cs b/src/Library/TerminalUI/Controls/Button.cs new file mode 100644 index 0000000..a349c0b --- /dev/null +++ b/src/Library/TerminalUI/Controls/Button.cs @@ -0,0 +1,94 @@ +using GeneralInputKey; +using PropertyChanged.SourceGenerator; +using TerminalUI.Color; +using TerminalUI.ConsoleDrivers; +using TerminalUI.Models; +using TerminalUI.Traits; + +namespace TerminalUI.Controls; + +public partial class Button : ContentView, T>, IFocusable +{ + private record RenderState(IColor? Foreground, IColor? Background); + + private Position? _cursorRenderPosition; + private RenderState? _lastRenderState; + + [Notify] private Position? _cursorPosition; + private List>> _clickHandlers = new(); + + public Button() + { + RerenderProperties.Add(nameof(CursorPosition)); + } + + public void Focus() + => ApplicationContext?.FocusManager.SetFocus(this); + + public void UnFocus() + => ApplicationContext?.FocusManager.UnFocus(this); + + public void SetCursorPosition(IConsoleDriver consoleDriver) + { + if (_cursorRenderPosition is null) return; + consoleDriver.SetCursorPosition(_cursorRenderPosition.Value); + } + + protected override Size CalculateSize() + { + if (Content is null || !Content.IsVisible) return new Size(0, 0); + + return Content.GetRequestedSize(); + } + + protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size) + { + if (ContentRendererMethod is null) + { + throw new NullReferenceException( + nameof(ContentRendererMethod) + + " is null, cannot render content of " + + Content?.GetType().Name + + " with DataContext of " + + DataContext?.GetType().Name); + } + + var backgroundColor = Background ?? renderContext.Background; + var foregroundColor = Foreground ?? renderContext.Foreground; + + var renderState = new RenderState(foregroundColor, backgroundColor); + + var forceRerender = !renderContext.ForceRerender && !NeedsRerender(renderState); + _lastRenderState = renderState; + + var childRenderContext = renderContext with + { + Background = backgroundColor, + Foreground = foregroundColor, + ForceRerender = forceRerender + }; + + _cursorRenderPosition = position; + + return ContentRendererMethod(childRenderContext, position, size); + } + + private bool NeedsRerender(RenderState renderState) => renderState != _lastRenderState; + + public override void HandleKeyInput(GeneralKeyEventArgs keyEventArgs) + { + if (keyEventArgs.Key != Keys.Enter) return; + + keyEventArgs.Handled = true; + foreach (var clickHandler in _clickHandlers) + { + clickHandler(this); + } + } + + public Button WithClickHandler(Action> handler) + { + _clickHandlers.Add(handler); + return this; + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/ItemsControl.cs b/src/Library/TerminalUI/Controls/ItemsControl.cs index a026062..71039fa 100644 --- a/src/Library/TerminalUI/Controls/ItemsControl.cs +++ b/src/Library/TerminalUI/Controls/ItemsControl.cs @@ -125,8 +125,7 @@ public sealed partial class ItemsControl { if (!child.IsVisible) continue; - if (!_requestedSizes.TryGetValue(child, out var childSize)) throw new Exception("Child size not found"); - + if (!_requestedSizes.TryGetValue(child, out var childSize)) continue; var childPosition = Orientation == Orientation.Vertical ? position with {Y = position.Y + delta} diff --git a/src/Library/TerminalUI/EventLoop.cs b/src/Library/TerminalUI/EventLoop.cs index cfb900f..c60ae2d 100644 --- a/src/Library/TerminalUI/EventLoop.cs +++ b/src/Library/TerminalUI/EventLoop.cs @@ -8,6 +8,7 @@ public class EventLoop : IEventLoop private readonly ILogger _logger; private readonly List _initializers = new(); private readonly List _permanentQueue = new(); + private readonly List _finalizers = new(); public int ThreadId { get; set; } = -1; @@ -21,6 +22,7 @@ public class EventLoop : IEventLoop public void AddToPermanentQueue(Action action) => _permanentQueue.Add(action); public void AddInitializer(Action action) => _initializers.Add(action); + public void AddFinalizer(Action action) => _finalizers.Add(action); public void Run() { @@ -35,6 +37,10 @@ public class EventLoop : IEventLoop ProcessQueues(); Thread.Sleep(10); } + foreach (var finalizer in _finalizers) + { + finalizer(); + } ThreadId = -1; } diff --git a/src/Library/TerminalUI/IEventLoop.cs b/src/Library/TerminalUI/IEventLoop.cs index 0ea07d5..3131b58 100644 --- a/src/Library/TerminalUI/IEventLoop.cs +++ b/src/Library/TerminalUI/IEventLoop.cs @@ -6,4 +6,5 @@ public interface IEventLoop void AddToPermanentQueue(Action action); void AddInitializer(Action action); int ThreadId { get; set; } + void AddFinalizer(Action action); } \ No newline at end of file diff --git a/src/Library/TerminalUI/RenderEngine.cs b/src/Library/TerminalUI/RenderEngine.cs index 8638fbf..28d38e6 100644 --- a/src/Library/TerminalUI/RenderEngine.cs +++ b/src/Library/TerminalUI/RenderEngine.cs @@ -33,6 +33,10 @@ public class RenderEngine : IRenderEngine _applicationContext.ConsoleDriver.ThreadId = _eventLoop.ThreadId; _applicationContext.ConsoleDriver.EnterRestrictedMode(); }); + _eventLoop.AddFinalizer(() => + { + _applicationContext.ConsoleDriver.ExitRestrictedMode(); + }); } public void RequestRerender(IView view) => RequestRerender();