Console DialogService
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
using ObservableComputations;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public interface IDialogServiceBase : IUserCommunicationService
|
||||||
|
{
|
||||||
|
ScalarComputing<ReadInputsViewModel?> ReadInput { get; }
|
||||||
|
ScalarComputing<MessageBoxViewModel?> LastMessageBox { get; }
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using DynamicData;
|
using System.Collections.ObjectModel;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
namespace FileTime.App.Core.Services;
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
public interface IModalService
|
public interface IModalService
|
||||||
{
|
{
|
||||||
IObservable<IChangeSet<IModalViewModel>> OpenModals { get; }
|
ReadOnlyObservableCollection<IModalViewModel> OpenModals { get; }
|
||||||
|
|
||||||
void OpenModal(IModalViewModel modalToOpen);
|
void OpenModal(IModalViewModel modalToOpen);
|
||||||
void CloseModal(IModalViewModel modalToClose);
|
void CloseModal(IModalViewModel modalToClose);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using FileTime.App.Core.ViewModels;
|
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using MvvmGen;
|
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
[ViewModel]
|
public class MessageBoxViewModel : IModalViewModel
|
||||||
public partial class MessageBoxViewModel : IModalViewModel
|
|
||||||
{
|
{
|
||||||
private readonly Action<MessageBoxViewModel, MessageBoxResult> _handler;
|
private readonly Action<MessageBoxViewModel, MessageBoxResult> _handler;
|
||||||
public string Text { get; }
|
public string Text { get; }
|
||||||
@@ -19,7 +16,7 @@ public partial class MessageBoxViewModel : IModalViewModel
|
|||||||
Action<MessageBoxViewModel, MessageBoxResult> handler,
|
Action<MessageBoxViewModel, MessageBoxResult> handler,
|
||||||
bool showCancel = true,
|
bool showCancel = true,
|
||||||
string? okText = null,
|
string? okText = null,
|
||||||
string? cancelText = null) : this()
|
string? cancelText = null)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
Text = text;
|
Text = text;
|
||||||
@@ -28,9 +25,7 @@ public partial class MessageBoxViewModel : IModalViewModel
|
|||||||
CancelText = cancelText ?? "No";
|
CancelText = cancelText ?? "No";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command]
|
|
||||||
public void Ok() => _handler.Invoke(this, MessageBoxResult.Ok);
|
public void Ok() => _handler.Invoke(this, MessageBoxResult.Ok);
|
||||||
|
|
||||||
[Command]
|
|
||||||
public void Cancel() => _handler.Invoke(this, MessageBoxResult.Cancel);
|
public void Cancel() => _handler.Invoke(this, MessageBoxResult.Cancel);
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using FileTime.App.Core.ViewModels;
|
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
public class ReadInputsViewModel : IModalViewModel
|
public class ReadInputsViewModel : IModalViewModel
|
||||||
{
|
{
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
using DeclarativeProperty;
|
using DeclarativeProperty;
|
||||||
using FileTime.App.Core.Configuration;
|
using FileTime.App.Core.Configuration;
|
||||||
using FileTime.App.Core.Extensions;
|
using FileTime.App.Core.Extensions;
|
||||||
@@ -23,7 +24,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
||||||
private readonly IPossibleCommandsService _possibleCommandsService;
|
private readonly IPossibleCommandsService _possibleCommandsService;
|
||||||
private readonly BindedCollection<IModalViewModel> _openModals;
|
private readonly ReadOnlyObservableCollection<IModalViewModel> _openModals;
|
||||||
|
|
||||||
public DefaultModeKeyInputHandler(
|
public DefaultModeKeyInputHandler(
|
||||||
IAppState appState,
|
IAppState appState,
|
||||||
@@ -46,7 +47,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
.Map(t => t?.CurrentLocation)
|
.Map(t => t?.CurrentLocation)
|
||||||
.Switch();
|
.Switch();
|
||||||
|
|
||||||
_openModals = modalService.OpenModals.ToBindedCollection();
|
_openModals = modalService.OpenModals;
|
||||||
|
|
||||||
_keysToSkip.Add(new[] {new KeyConfig(Keys.Up)});
|
_keysToSkip.Add(new[] {new KeyConfig(Keys.Up)});
|
||||||
_keysToSkip.Add(new[] {new KeyConfig(Keys.Down)});
|
_keysToSkip.Add(new[] {new KeyConfig(Keys.Down)});
|
||||||
@@ -75,9 +76,9 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
{
|
{
|
||||||
var doGeneralReset = _appState.PreviousKeys.Count > 1;
|
var doGeneralReset = _appState.PreviousKeys.Count > 1;
|
||||||
|
|
||||||
if ((_openModals.Collection?.Count ?? 0) > 0)
|
if (_openModals.Count > 0)
|
||||||
{
|
{
|
||||||
_modalService.CloseModal(_openModals.Collection!.Last());
|
_modalService.CloseModal(_openModals.Last());
|
||||||
}
|
}
|
||||||
else if (_currentLocation.Value?.GetExtension<EscHandlerContainerExtension>() is { } escHandler)
|
else if (_currentLocation.Value?.GetExtension<EscHandlerContainerExtension>() is { } escHandler)
|
||||||
{
|
{
|
||||||
|
|||||||
130
src/AppCommon/FileTime.App.Core/Services/DialogServiceBase.cs
Normal file
130
src/AppCommon/FileTime.App.Core/Services/DialogServiceBase.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using DynamicData;
|
||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
using ObservableComputations;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public abstract class DialogServiceBase : IDialogServiceBase
|
||||||
|
{
|
||||||
|
private readonly IModalService _modalService;
|
||||||
|
private OcConsumer _readInputConsumer = new();
|
||||||
|
private OcConsumer _lastMessageBoxConsumer = new();
|
||||||
|
public ScalarComputing<ReadInputsViewModel?> ReadInput { get; }
|
||||||
|
public ScalarComputing<MessageBoxViewModel?> LastMessageBox { get; }
|
||||||
|
|
||||||
|
protected DialogServiceBase(IModalService modalService)
|
||||||
|
{
|
||||||
|
_modalService = modalService;
|
||||||
|
ReadInput = modalService
|
||||||
|
.OpenModals
|
||||||
|
.OfTypeComputing<ReadInputsViewModel>()
|
||||||
|
.FirstComputing()
|
||||||
|
.For(_readInputConsumer);
|
||||||
|
|
||||||
|
LastMessageBox =
|
||||||
|
modalService
|
||||||
|
.OpenModals
|
||||||
|
.OfTypeComputing<MessageBoxViewModel>()
|
||||||
|
.LastComputing()
|
||||||
|
.For(_lastMessageBoxConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadInputs(
|
||||||
|
IEnumerable<IInputElement> inputs,
|
||||||
|
Action inputHandler,
|
||||||
|
Action? cancelHandler = null,
|
||||||
|
IEnumerable<IPreviewElement>? previews = null)
|
||||||
|
{
|
||||||
|
var modalViewModel = new ReadInputsViewModel
|
||||||
|
{
|
||||||
|
Inputs = inputs.ToList(),
|
||||||
|
SuccessHandler = HandleReadInputsSuccess,
|
||||||
|
CancelHandler = HandleReadInputsCancel
|
||||||
|
};
|
||||||
|
|
||||||
|
if (previews is not null)
|
||||||
|
{
|
||||||
|
modalViewModel.Previews.AddRange(previews);
|
||||||
|
}
|
||||||
|
|
||||||
|
_modalService.OpenModal(modalViewModel);
|
||||||
|
|
||||||
|
void HandleReadInputsSuccess(ReadInputsViewModel readInputsViewModel)
|
||||||
|
{
|
||||||
|
_modalService.CloseModal(readInputsViewModel);
|
||||||
|
inputHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleReadInputsCancel(ReadInputsViewModel readInputsViewModel)
|
||||||
|
{
|
||||||
|
_modalService.CloseModal(readInputsViewModel);
|
||||||
|
cancelHandler?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> ReadInputs(IEnumerable<IInputElement> fields, IEnumerable<IPreviewElement>? previews = null)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
ReadInputs(
|
||||||
|
fields,
|
||||||
|
() => taskCompletionSource.SetResult(true),
|
||||||
|
() => taskCompletionSource.SetResult(false),
|
||||||
|
previews
|
||||||
|
);
|
||||||
|
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task<bool> ReadInputs(params IInputElement[] fields)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
ReadInputs(
|
||||||
|
fields,
|
||||||
|
() => taskCompletionSource.SetResult(true),
|
||||||
|
() => taskCompletionSource.SetResult(false)
|
||||||
|
);
|
||||||
|
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> ReadInputs(IInputElement field, IEnumerable<IPreviewElement>? previews = null)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
ReadInputs(
|
||||||
|
new[] {field},
|
||||||
|
() => taskCompletionSource.SetResult(true),
|
||||||
|
() => taskCompletionSource.SetResult(false),
|
||||||
|
previews
|
||||||
|
);
|
||||||
|
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void ShowToastMessage(string text);
|
||||||
|
|
||||||
|
public Task<MessageBoxResult> ShowMessageBox(
|
||||||
|
string text,
|
||||||
|
bool showCancel = true,
|
||||||
|
string? okText = null,
|
||||||
|
string? cancelText = null)
|
||||||
|
{
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<MessageBoxResult>();
|
||||||
|
_modalService.OpenModal(
|
||||||
|
new MessageBoxViewModel(
|
||||||
|
text,
|
||||||
|
(vm, result) =>
|
||||||
|
{
|
||||||
|
_modalService.CloseModal(vm);
|
||||||
|
taskCompletionSource.SetResult(result);
|
||||||
|
},
|
||||||
|
showCancel,
|
||||||
|
okText,
|
||||||
|
cancelText
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return taskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -7,14 +8,14 @@ namespace FileTime.App.Core.Services;
|
|||||||
public class ModalService : IModalService
|
public class ModalService : IModalService
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly SourceList<IModalViewModel> _openModals = new();
|
private readonly ObservableCollection<IModalViewModel> _openModals = new();
|
||||||
public IObservable<IChangeSet<IModalViewModel>> OpenModals { get; }
|
public ReadOnlyObservableCollection<IModalViewModel> OpenModals { get; }
|
||||||
public event EventHandler? AllModalClosed;
|
public event EventHandler? AllModalClosed;
|
||||||
|
|
||||||
public ModalService(IServiceProvider serviceProvider)
|
public ModalService(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
OpenModals = _openModals.Connect().StartWithEmpty();
|
OpenModals = new ReadOnlyObservableCollection<IModalViewModel>(_openModals);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen);
|
public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private readonly ILogger<RapidTravelModeKeyInputHandler> _logger;
|
private readonly ILogger<RapidTravelModeKeyInputHandler> _logger;
|
||||||
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
||||||
private readonly BindedCollection<IModalViewModel> _openModals;
|
|
||||||
private ITabViewModel? _selectedTab;
|
private ITabViewModel? _selectedTab;
|
||||||
|
|
||||||
public RapidTravelModeKeyInputHandler(
|
public RapidTravelModeKeyInputHandler(
|
||||||
@@ -41,8 +40,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
|
|
||||||
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
|
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
|
||||||
|
|
||||||
_openModals = modalService.OpenModals.ToBindedCollection();
|
|
||||||
|
|
||||||
_appState.RapidTravelTextDebounced.Subscribe((v, _) =>
|
_appState.RapidTravelTextDebounced.Subscribe((v, _) =>
|
||||||
{
|
{
|
||||||
if (_selectedTab?.Tab is not { } tab) return Task.CompletedTask;
|
if (_selectedTab?.Tab is not { } tab) return Task.CompletedTask;
|
||||||
@@ -62,9 +59,9 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
if (args.Key == Keys.Escape)
|
if (args.Key == Keys.Escape)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
if ((_openModals.Collection?.Count ?? 0) > 0)
|
if (_modalService.OpenModals.Count > 0)
|
||||||
{
|
{
|
||||||
_modalService.CloseModal(_openModals.Collection!.Last());
|
_modalService.CloseModal(_modalService.OpenModals.Last());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using FileTime.App.CommandPalette.ViewModels;
|
using FileTime.App.CommandPalette.ViewModels;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Services;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
@@ -10,4 +12,6 @@ public interface IRootViewModel
|
|||||||
string UserName { get; }
|
string UserName { get; }
|
||||||
string MachineName { get; }
|
string MachineName { get; }
|
||||||
ICommandPaletteViewModel CommandPalette { get; }
|
ICommandPaletteViewModel CommandPalette { get; }
|
||||||
|
IDialogService DialogService { get; }
|
||||||
|
event Action<IInputElement>? FocusReadInputElement;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.Services;
|
||||||
|
|
||||||
|
public interface IDialogService : IDialogServiceBase
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,11 +6,18 @@ using FileTime.ConsoleUI.App.KeyInputHandling;
|
|||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.ConsoleDrivers;
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
public class App : IApplication
|
public class App : IApplication
|
||||||
{
|
{
|
||||||
|
private static readonly List<Keys> KeysToFurtherProcess = new()
|
||||||
|
{
|
||||||
|
Keys.Enter,
|
||||||
|
Keys.Escape
|
||||||
|
};
|
||||||
|
|
||||||
private readonly ILifecycleService _lifecycleService;
|
private readonly ILifecycleService _lifecycleService;
|
||||||
|
|
||||||
private readonly IConsoleAppState _consoleAppState;
|
private readonly IConsoleAppState _consoleAppState;
|
||||||
@@ -86,11 +93,13 @@ public class App : IApplication
|
|||||||
SpecialKeysStatus = specialKeysStatus
|
SpecialKeysStatus = specialKeysStatus
|
||||||
};
|
};
|
||||||
|
|
||||||
if (focusManager.Focused is { } focused)
|
var focused = focusManager.Focused;
|
||||||
|
if (focused is { })
|
||||||
{
|
{
|
||||||
focused.HandleKeyInput(keyEventArgs);
|
focused.HandleKeyInput(keyEventArgs);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (focused is null || (!keyEventArgs.Handled && KeysToFurtherProcess.Contains(keyEventArgs.Key)))
|
||||||
{
|
{
|
||||||
_keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus);
|
_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>
|
new Border<IRootViewModel>
|
||||||
{
|
{
|
||||||
Margin = new Thickness(0, 0, 0, 2),
|
Margin = new Thickness(0, 0, 0, 1),
|
||||||
Content = inputTextBox
|
Content = inputTextBox
|
||||||
},
|
},
|
||||||
new ListView<IRootViewModel, ICommandPaletteEntryViewModel>
|
new ListView<IRootViewModel, ICommandPaletteEntryViewModel>
|
||||||
@@ -103,15 +103,13 @@ public class CommandPalette
|
|||||||
item.Bind(
|
item.Bind(
|
||||||
item.Parent,
|
item.Parent,
|
||||||
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
|
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
|
||||||
t => t.Background,
|
t => t.Background
|
||||||
v => v
|
|
||||||
);
|
);
|
||||||
|
|
||||||
item.Bind(
|
item.Bind(
|
||||||
item.Parent,
|
item.Parent,
|
||||||
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
|
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
|
||||||
t => t.Foreground,
|
t => t.Foreground
|
||||||
v => v
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ public class KeyInputHandlerService : IKeyInputHandlerService
|
|||||||
|
|
||||||
if (_appState.ViewMode.Value == ViewMode.Default)
|
if (_appState.ViewMode.Value == ViewMode.Default)
|
||||||
{
|
{
|
||||||
Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent)).Wait();
|
Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent));
|
||||||
}
|
}
|
||||||
else
|
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.App.Core.ViewModels;
|
||||||
using FileTime.ConsoleUI.App.Controls;
|
using FileTime.ConsoleUI.App.Controls;
|
||||||
using FileTime.ConsoleUI.App.Styling;
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
using GeneralInputKey;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
using TerminalUI.ViewExtensions;
|
using TerminalUI.ViewExtensions;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
@@ -18,9 +23,12 @@ public class MainWindow
|
|||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly ITheme _theme;
|
private readonly ITheme _theme;
|
||||||
private readonly CommandPalette _commandPalette;
|
private readonly CommandPalette _commandPalette;
|
||||||
|
|
||||||
private readonly Lazy<IView> _root;
|
private readonly Lazy<IView> _root;
|
||||||
|
|
||||||
|
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
|
||||||
|
private IInputElement? _inputElementToFocus;
|
||||||
|
private Action? _readInputChildHandlerUnsubscriber;
|
||||||
|
|
||||||
public MainWindow(
|
public MainWindow(
|
||||||
IRootViewModel rootViewModel,
|
IRootViewModel rootViewModel,
|
||||||
IApplicationContext applicationContext,
|
IApplicationContext applicationContext,
|
||||||
@@ -32,6 +40,40 @@ public class MainWindow
|
|||||||
_theme = theme;
|
_theme = theme;
|
||||||
_commandPalette = commandPalette;
|
_commandPalette = commandPalette;
|
||||||
_root = new Lazy<IView>(Initialize);
|
_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[]
|
public IEnumerable<IView> RootViews() => new[]
|
||||||
@@ -46,10 +88,33 @@ public class MainWindow
|
|||||||
Name = "root",
|
Name = "root",
|
||||||
DataContext = _rootViewModel,
|
DataContext = _rootViewModel,
|
||||||
ApplicationContext = _applicationContext,
|
ApplicationContext = _applicationContext,
|
||||||
|
Foreground = _theme.DefaultForegroundColor,
|
||||||
ChildInitializer =
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
MainContent(),
|
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;
|
return root;
|
||||||
@@ -188,7 +253,6 @@ public class MainWindow
|
|||||||
ItemTemplate = item =>
|
ItemTemplate = item =>
|
||||||
{
|
{
|
||||||
var textBlock = item.CreateChild<TextBlock<ITabViewModel>>();
|
var textBlock = item.CreateChild<TextBlock<ITabViewModel>>();
|
||||||
textBlock.Foreground = _theme.DefaultForegroundColor;
|
|
||||||
|
|
||||||
textBlock.Bind(
|
textBlock.Bind(
|
||||||
textBlock,
|
textBlock,
|
||||||
@@ -356,4 +420,137 @@ public class MainWindow
|
|||||||
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor,
|
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor,
|
||||||
_ => throw new NotImplementedException()
|
_ => 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.CommandPalette.ViewModels;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Services;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
@@ -10,14 +12,29 @@ public class RootViewModel : IRootViewModel
|
|||||||
public IPossibleCommandsViewModel PossibleCommands { get; }
|
public IPossibleCommandsViewModel PossibleCommands { get; }
|
||||||
public IConsoleAppState AppState { get; }
|
public IConsoleAppState AppState { get; }
|
||||||
public ICommandPaletteViewModel CommandPalette { get; }
|
public ICommandPaletteViewModel CommandPalette { get; }
|
||||||
|
public IDialogService DialogService { get; }
|
||||||
|
public event Action<IInputElement>? FocusReadInputElement;
|
||||||
|
|
||||||
public RootViewModel(
|
public RootViewModel(
|
||||||
IConsoleAppState appState,
|
IConsoleAppState appState,
|
||||||
IPossibleCommandsViewModel possibleCommands,
|
IPossibleCommandsViewModel possibleCommands,
|
||||||
ICommandPaletteViewModel commandPalette)
|
ICommandPaletteViewModel commandPalette,
|
||||||
|
IDialogService dialogService)
|
||||||
{
|
{
|
||||||
AppState = appState;
|
AppState = appState;
|
||||||
PossibleCommands = possibleCommands;
|
PossibleCommands = possibleCommands;
|
||||||
CommandPalette = commandPalette;
|
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<IApplication, App>();
|
||||||
services.TryAddSingleton<IConsoleAppState, ConsoleAppState>();
|
services.TryAddSingleton<IConsoleAppState, ConsoleAppState>();
|
||||||
services.TryAddSingleton<IAppState>(sp => sp.GetRequiredService<IConsoleAppState>());
|
services.TryAddSingleton<IAppState>(sp => sp.GetRequiredService<IConsoleAppState>());
|
||||||
services.TryAddSingleton<IUserCommunicationService, ConsoleUserCommunicationService>();
|
|
||||||
services.TryAddSingleton<IKeyInputHandlerService, KeyInputHandlerService>();
|
services.TryAddSingleton<IKeyInputHandlerService, KeyInputHandlerService>();
|
||||||
services.TryAddSingleton<IAppKeyService<ConsoleKey>, ConsoleAppKeyService>();
|
services.TryAddSingleton<IAppKeyService<ConsoleKey>, ConsoleAppKeyService>();
|
||||||
services.TryAddSingleton<ISystemClipboardService, ConsoleSystemClipboardService>();
|
services.TryAddSingleton<ISystemClipboardService, ConsoleSystemClipboardService>();
|
||||||
services.AddSingleton<CustomLoggerSink>();
|
services.AddSingleton<CustomLoggerSink>();
|
||||||
services.TryAddSingleton(new ApplicationConfiguration(true));
|
services.TryAddSingleton(new ApplicationConfiguration(true));
|
||||||
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
||||||
|
services.TryAddSingleton<IDialogService, DialogService>();
|
||||||
|
services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>());
|
||||||
|
|
||||||
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
||||||
return services;
|
return services;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
<ProjectReference Include="..\..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
||||||
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.LocalAdmin.Abstractions\FileTime.Providers.LocalAdmin.Abstractions.csproj" />
|
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.LocalAdmin.Abstractions\FileTime.Providers.LocalAdmin.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
using FileTime.Core.Interactions;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.GuiApp.App.ViewModels;
|
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.Services;
|
namespace FileTime.GuiApp.App.Services;
|
||||||
|
|
||||||
public interface IDialogService : IUserCommunicationService
|
public interface IDialogService : IDialogServiceBase
|
||||||
{
|
{
|
||||||
IObservable<ReadInputsViewModel?> ReadInput { get; }
|
|
||||||
IObservable<MessageBoxViewModel?> LastMessageBox { get; }
|
|
||||||
}
|
}
|
||||||
@@ -1,145 +1,24 @@
|
|||||||
using System.Reactive.Linq;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DynamicData;
|
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.Core.Interactions;
|
|
||||||
using FileTime.GuiApp.App.ViewModels;
|
using FileTime.GuiApp.App.ViewModels;
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.Services;
|
namespace FileTime.GuiApp.App.Services;
|
||||||
|
|
||||||
public class DialogService : IDialogService
|
public class DialogService : DialogServiceBase, IDialogService
|
||||||
{
|
{
|
||||||
private readonly IModalService _modalService;
|
|
||||||
private readonly IGuiAppState _guiAppState;
|
private readonly IGuiAppState _guiAppState;
|
||||||
|
|
||||||
public IObservable<ReadInputsViewModel?> ReadInput { get; }
|
public DialogService(IModalService modalService, IGuiAppState guiAppState) : base(modalService)
|
||||||
public IObservable<MessageBoxViewModel?> LastMessageBox { get; }
|
|
||||||
|
|
||||||
public DialogService(IModalService modalService, IGuiAppState guiAppState)
|
|
||||||
{
|
{
|
||||||
_modalService = modalService;
|
|
||||||
_guiAppState = guiAppState;
|
_guiAppState = guiAppState;
|
||||||
ReadInput = modalService
|
|
||||||
.OpenModals
|
|
||||||
.ToCollection()
|
|
||||||
.Select(modals =>
|
|
||||||
(ReadInputsViewModel?) modals.FirstOrDefault(m => m is ReadInputsViewModel)
|
|
||||||
)
|
|
||||||
.Publish(null)
|
|
||||||
.RefCount();
|
|
||||||
|
|
||||||
LastMessageBox =
|
|
||||||
modalService
|
|
||||||
.OpenModals
|
|
||||||
.Filter(m => m is MessageBoxViewModel)
|
|
||||||
.Transform(m => (MessageBoxViewModel) m)
|
|
||||||
.ToCollection()
|
|
||||||
.Select(m => m.LastOrDefault());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadInputs(
|
public override void ShowToastMessage(string text)
|
||||||
IEnumerable<IInputElement> inputs,
|
=>
|
||||||
Action inputHandler,
|
|
||||||
Action? cancelHandler = null,
|
|
||||||
IEnumerable<IPreviewElement>? previews = null)
|
|
||||||
{
|
|
||||||
var modalViewModel = new ReadInputsViewModel
|
|
||||||
{
|
|
||||||
Inputs = inputs.ToList(),
|
|
||||||
SuccessHandler = HandleReadInputsSuccess,
|
|
||||||
CancelHandler = HandleReadInputsCancel
|
|
||||||
};
|
|
||||||
|
|
||||||
if (previews is not null)
|
|
||||||
{
|
|
||||||
modalViewModel.Previews.AddRange(previews);
|
|
||||||
}
|
|
||||||
|
|
||||||
_modalService.OpenModal(modalViewModel);
|
|
||||||
|
|
||||||
void HandleReadInputsSuccess(ReadInputsViewModel readInputsViewModel)
|
|
||||||
{
|
|
||||||
_modalService.CloseModal(readInputsViewModel);
|
|
||||||
inputHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleReadInputsCancel(ReadInputsViewModel readInputsViewModel)
|
|
||||||
{
|
|
||||||
_modalService.CloseModal(readInputsViewModel);
|
|
||||||
cancelHandler?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> ReadInputs(IEnumerable<IInputElement> fields, IEnumerable<IPreviewElement>? previews = null)
|
|
||||||
{
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
ReadInputs(
|
|
||||||
fields,
|
|
||||||
() => taskCompletionSource.SetResult(true),
|
|
||||||
() => taskCompletionSource.SetResult(false),
|
|
||||||
previews
|
|
||||||
);
|
|
||||||
|
|
||||||
return taskCompletionSource.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Task<bool> ReadInputs(params IInputElement[] fields)
|
|
||||||
{
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
ReadInputs(
|
|
||||||
fields,
|
|
||||||
() => taskCompletionSource.SetResult(true),
|
|
||||||
() => taskCompletionSource.SetResult(false)
|
|
||||||
);
|
|
||||||
|
|
||||||
return taskCompletionSource.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> ReadInputs(IInputElement field, IEnumerable<IPreviewElement>? previews = null)
|
|
||||||
{
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
ReadInputs(
|
|
||||||
new[] {field},
|
|
||||||
() => taskCompletionSource.SetResult(true),
|
|
||||||
() => taskCompletionSource.SetResult(false),
|
|
||||||
previews
|
|
||||||
);
|
|
||||||
|
|
||||||
return taskCompletionSource.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowToastMessage(string text)
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Add(text));
|
await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Add(text));
|
||||||
await Task.Delay(5000);
|
await Task.Delay(5000);
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Remove(text));
|
await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Remove(text));
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public Task<MessageBoxResult> ShowMessageBox(
|
|
||||||
string text,
|
|
||||||
bool showCancel = true,
|
|
||||||
string? okText = null,
|
|
||||||
string? cancelText = null)
|
|
||||||
{
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<MessageBoxResult>();
|
|
||||||
_modalService.OpenModal(
|
|
||||||
new MessageBoxViewModel(
|
|
||||||
text,
|
|
||||||
(vm, result) =>
|
|
||||||
{
|
|
||||||
_modalService.CloseModal(vm);
|
|
||||||
taskCompletionSource.SetResult(result);
|
|
||||||
},
|
|
||||||
showCancel,
|
|
||||||
okText,
|
|
||||||
cancelText
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return taskCompletionSource.Task;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -22,8 +22,6 @@
|
|||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:appCoreModels="using:FileTime.App.Core.Models"
|
xmlns:appCoreModels="using:FileTime.App.Core.Models"
|
||||||
xmlns:appInteractions="using:FileTime.App.Core.Interactions"
|
xmlns:appInteractions="using:FileTime.App.Core.Interactions"
|
||||||
xmlns:config="using:FileTime.GuiApp.App.Configuration"
|
|
||||||
xmlns:configuration="clr-namespace:FileTime.App.Core.Configuration;assembly=FileTime.App.Core.Abstraction"
|
|
||||||
xmlns:corevm="using:FileTime.App.Core.ViewModels"
|
xmlns:corevm="using:FileTime.App.Core.ViewModels"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
|
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
|
||||||
@@ -787,7 +785,7 @@
|
|||||||
<Border
|
<Border
|
||||||
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
IsVisible="{Binding DialogService.ReadInput^, Converter={x:Static ObjectConverters.IsNotNull}}"
|
IsVisible="{Binding DialogService.ReadInput.Value, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
x:Name="ReadInputContainer">
|
x:Name="ReadInputContainer">
|
||||||
<Border
|
<Border
|
||||||
@@ -798,7 +796,7 @@
|
|||||||
<Grid RowDefinitions="Auto,Auto,Auto">
|
<Grid RowDefinitions="Auto,Auto,Auto">
|
||||||
|
|
||||||
<ItemsControl
|
<ItemsControl
|
||||||
ItemsSource="{Binding DialogService.ReadInput^.Inputs}"
|
ItemsSource="{Binding DialogService.ReadInput.Value.Inputs}"
|
||||||
KeyUp="InputList_OnKeyUp"
|
KeyUp="InputList_OnKeyUp"
|
||||||
x:Name="InputList">
|
x:Name="InputList">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
@@ -839,7 +837,7 @@
|
|||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding DialogService.ReadInput^.Previews}">
|
<ItemsControl Grid.Row="1" ItemsSource="{Binding DialogService.ReadInput.Value.Previews}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -913,7 +911,7 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
DataContext="{Binding DialogService.ReadInput^}"
|
DataContext="{Binding DialogService.ReadInput.Value}"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
@@ -935,7 +933,7 @@
|
|||||||
|
|
||||||
<Border
|
<Border
|
||||||
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
||||||
DataContext="{Binding DialogService.LastMessageBox^}"
|
DataContext="{Binding DialogService.LastMessageBox.Value}"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
IsVisible="{Binding Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
|
IsVisible="{Binding Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
@@ -952,12 +950,12 @@
|
|||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding OkCommand}"
|
Command="{Binding Ok}"
|
||||||
Content="{Binding OkText}"
|
Content="{Binding OkText}"
|
||||||
HorizontalContentAlignment="Center"
|
HorizontalContentAlignment="Center"
|
||||||
Width="80" />
|
Width="80" />
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding CancelCommand}"
|
Command="{Binding Cancel}"
|
||||||
Content="{Binding CancelText}"
|
Content="{Binding CancelText}"
|
||||||
HorizontalContentAlignment="Center"
|
HorizontalContentAlignment="Center"
|
||||||
IsVisible="{Binding ShowCancel}"
|
IsVisible="{Binding ShowCancel}"
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ public partial class MainWindow : Window, IUiAccessor
|
|||||||
private readonly Action? _initializer;
|
private readonly Action? _initializer;
|
||||||
private ILogger<MainWindow>? _logger;
|
private ILogger<MainWindow>? _logger;
|
||||||
private IModalService? _modalService;
|
private IModalService? _modalService;
|
||||||
private IReadOnlyCollection<IModalViewModel>? _openModals;
|
|
||||||
private ReadInputsViewModel? _inputViewModel;
|
|
||||||
private IDisposable? _inputViewModelSubscription;
|
|
||||||
private bool _isShuttingDown;
|
private bool _isShuttingDown;
|
||||||
private bool _shutdownCompleted;
|
private bool _shutdownCompleted;
|
||||||
private readonly object _isClosingLock = new();
|
private readonly object _isClosingLock = new();
|
||||||
@@ -61,19 +58,9 @@ public partial class MainWindow : Window, IUiAccessor
|
|||||||
|
|
||||||
_logger = DI.ServiceProvider.GetService<ILogger<MainWindow>>();
|
_logger = DI.ServiceProvider.GetService<ILogger<MainWindow>>();
|
||||||
_modalService = DI.ServiceProvider.GetRequiredService<IModalService>();
|
_modalService = DI.ServiceProvider.GetRequiredService<IModalService>();
|
||||||
_modalService.OpenModals.ToCollection().Subscribe(m => _openModals = m);
|
DI.ServiceProvider.GetRequiredService<SystemClipboardService>().UiAccessor = this;
|
||||||
DI.ServiceProvider.GetRequiredService<Services.SystemClipboardService>().UiAccessor = this;
|
|
||||||
|
|
||||||
ReadInputContainer.PropertyChanged += ReadInputContainerOnPropertyChanged;
|
ReadInputContainer.PropertyChanged += ReadInputContainerOnPropertyChanged;
|
||||||
DataContextChanged += (_, _) =>
|
|
||||||
{
|
|
||||||
if (DataContext is not MainWindowViewModel mainWindowViewModel) return;
|
|
||||||
|
|
||||||
_inputViewModelSubscription?.Dispose();
|
|
||||||
_inputViewModelSubscription = mainWindowViewModel.DialogService.ReadInput.Subscribe(
|
|
||||||
inputViewModel => _inputViewModel = inputViewModel
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger?.LogInformation(
|
_logger?.LogInformation(
|
||||||
$"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization...");
|
$"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization...");
|
||||||
@@ -99,7 +86,7 @@ public partial class MainWindow : Window, IUiAccessor
|
|||||||
|
|
||||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if ((_openModals?.Count ?? 0) > 0) return;
|
if (_modalService!.OpenModals.Count > 0) return;
|
||||||
ViewModel?.ProcessKeyDown(e);
|
ViewModel?.ProcessKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,15 +164,14 @@ public partial class MainWindow : Window, IUiAccessor
|
|||||||
|
|
||||||
private void InputList_OnKeyUp(object? sender, KeyEventArgs e)
|
private void InputList_OnKeyUp(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
|
var inputViewModel = ViewModel!.DialogService.ReadInput.Value;
|
||||||
if (e.Key == Key.Escape)
|
if (e.Key == Key.Escape)
|
||||||
{
|
{
|
||||||
_inputViewModel?.Cancel();
|
inputViewModel?.Cancel();
|
||||||
_inputViewModel = null;
|
|
||||||
}
|
}
|
||||||
else if (e.Key == Key.Enter)
|
else if (e.Key == Key.Enter)
|
||||||
{
|
{
|
||||||
_inputViewModel?.Process();
|
inputViewModel?.Process();
|
||||||
_inputViewModel = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace ObservableComputations;
|
namespace ObservableComputations;
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
[ObservableComputationsCall]
|
||||||
|
public static Filtering<TSourceItem> Filtering<TSourceItem>(
|
||||||
|
this ReadOnlyObservableCollection<TSourceItem> source,
|
||||||
|
Expression<Func<TSourceItem, bool>> predicateExpression,
|
||||||
|
int initialCapacity = 0)
|
||||||
|
=> new(source, predicateExpression, initialCapacity);
|
||||||
|
|
||||||
[ObservableComputationsCall]
|
[ObservableComputationsCall]
|
||||||
public static Selecting<TSourceItem, TResultItem> Selecting<TSourceItem, TResultItem>(
|
public static Selecting<TSourceItem, TResultItem> Selecting<TSourceItem, TResultItem>(
|
||||||
this ReadOnlyObservableCollection<TSourceItem> source,
|
this ReadOnlyObservableCollection<TSourceItem> source,
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ public class DotnetDriver : IConsoleDriver
|
|||||||
return new(x, y);
|
return new(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text) => Console.Write(text);
|
public void Write(string text) => Console.Out.Write(text);
|
||||||
|
public void Write(ReadOnlySpan<char> text) => Console.Out.Write(text);
|
||||||
|
|
||||||
public void Write(char text) => Console.Write(text);
|
public void Write(char text) => Console.Out.Write(text);
|
||||||
|
|
||||||
public virtual void Dispose() => Console.Clear();
|
public virtual void Dispose() => Console.Clear();
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public interface IConsoleDriver
|
|||||||
void ResetColor();
|
void ResetColor();
|
||||||
Position GetCursorPosition();
|
Position GetCursorPosition();
|
||||||
void Write(string text);
|
void Write(string text);
|
||||||
|
void Write(ReadOnlySpan<char> text);
|
||||||
void Write(char text);
|
void Write(char text);
|
||||||
bool CanRead();
|
bool CanRead();
|
||||||
ConsoleKeyInfo ReadKey();
|
ConsoleKeyInfo ReadKey();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public partial class Border<T> : ContentView<T>, IDisplayView
|
public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
||||||
{
|
{
|
||||||
[Notify] private Thickness _borderThickness = 1;
|
[Notify] private Thickness _borderThickness = 1;
|
||||||
[Notify] private Thickness _padding = 0;
|
[Notify] private Thickness _padding = 0;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
public abstract class ChildContainerView<TConcrete, T>
|
||||||
|
: View<TConcrete, T>, IChildContainer<T>
|
||||||
|
where TConcrete : View<TConcrete, T>
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<IView> _children = new();
|
private readonly ObservableCollection<IView> _children = new();
|
||||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public abstract partial class ContentView<T> : View<T>, IContentRenderer<T>
|
public abstract partial class ContentView<TConcrete, T>
|
||||||
|
: View<TConcrete, T>, IContentRenderer<T>
|
||||||
|
where TConcrete : View<TConcrete, T>
|
||||||
{
|
{
|
||||||
private bool _placeholderRenderDone;
|
private bool _placeholderRenderDone;
|
||||||
[Notify] private RenderMethod _contentRendererMethod;
|
[Notify] private RenderMethod _contentRendererMethod;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using TerminalUI.ViewExtensions;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public class Grid<T> : ChildContainerView<T>, IVisibilityChangeHandler
|
public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeHandler
|
||||||
{
|
{
|
||||||
private readonly List<IView> _forceRerenderChildren = new();
|
private readonly List<IView> _forceRerenderChildren = new();
|
||||||
private readonly object _forceRerenderChildrenLock = new();
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.ComponentModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using GeneralInputKey;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
@@ -28,10 +30,12 @@ public interface IView : INotifyPropertyChanged, IDisposableCollection
|
|||||||
List<object> Extensions { get; }
|
List<object> Extensions { get; }
|
||||||
RenderMethod RenderMethod { get; set; }
|
RenderMethod RenderMethod { get; set; }
|
||||||
IView? VisualParent { get; set; }
|
IView? VisualParent { get; set; }
|
||||||
|
ReadOnlyObservableCollection<IView> VisualChildren { get; }
|
||||||
event Action<IView> Disposed;
|
event Action<IView> Disposed;
|
||||||
|
|
||||||
Size GetRequestedSize();
|
Size GetRequestedSize();
|
||||||
bool Render(in RenderContext renderContext, Position position, Size size);
|
bool Render(in RenderContext renderContext, Position position, Size size);
|
||||||
|
void HandleKeyInput(GeneralKeyEventArgs keyEventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IView<T> : IView
|
public interface IView<T> : IView
|
||||||
|
|||||||
194
src/Library/TerminalUI/Controls/ItemsControl.cs
Normal file
194
src/Library/TerminalUI/Controls/ItemsControl.cs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using ObservableComputations;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
|
public sealed partial class ItemsControl<TDataContext, TItem>
|
||||||
|
: View<ItemsControl<TDataContext, TItem>, TDataContext>, IVisibilityChangeHandler
|
||||||
|
{
|
||||||
|
private readonly List<IView> _forceRerenderChildren = new();
|
||||||
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
|
private readonly List<IDisposable> _itemsDisposables = new();
|
||||||
|
private readonly Dictionary<IView, Size> _requestedSizes = new();
|
||||||
|
private IList<IView<TItem>> _children = new List<IView<TItem>>();
|
||||||
|
private object? _itemsSource;
|
||||||
|
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||||
|
|
||||||
|
public Func<IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||||
|
|
||||||
|
public IReadOnlyList<IView<TItem>> Children => _children.AsReadOnly();
|
||||||
|
|
||||||
|
public object? ItemsSource
|
||||||
|
{
|
||||||
|
get => _itemsSource;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_itemsSource == value) return;
|
||||||
|
_itemsSource = value;
|
||||||
|
|
||||||
|
foreach (var disposable in _itemsDisposables)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_itemsDisposables.Clear();
|
||||||
|
|
||||||
|
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
|
||||||
|
{
|
||||||
|
_children = observableDeclarative
|
||||||
|
.Selecting(i => CreateItem(i))
|
||||||
|
.OfTypeComputing<IView<TItem>>();
|
||||||
|
}
|
||||||
|
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
|
||||||
|
{
|
||||||
|
_children = readOnlyObservableDeclarative
|
||||||
|
.Selecting(i => CreateItem(i))
|
||||||
|
.OfTypeComputing<IView<TItem>>();
|
||||||
|
}
|
||||||
|
else if (_itemsSource is ICollection<TItem> collection)
|
||||||
|
_children = collection.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||||
|
else if (_itemsSource is TItem[] array)
|
||||||
|
_children = array.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||||
|
else if (_itemsSource is IEnumerable<TItem> enumerable)
|
||||||
|
_children = enumerable.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||||
|
else if (value is null)
|
||||||
|
{
|
||||||
|
_children = new List<IView<TItem>>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(Children));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemsControl()
|
||||||
|
{
|
||||||
|
RerenderProperties.Add(nameof(ItemsSource));
|
||||||
|
RerenderProperties.Add(nameof(Orientation));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override Size CalculateSize()
|
||||||
|
{
|
||||||
|
_requestedSizes.Clear();
|
||||||
|
double width = 0;
|
||||||
|
double height = 0;
|
||||||
|
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
if (!child.IsVisible) continue;
|
||||||
|
|
||||||
|
var childSize = child.GetRequestedSize();
|
||||||
|
_requestedSizes.Add(child, childSize);
|
||||||
|
|
||||||
|
if (Orientation == Orientation.Vertical)
|
||||||
|
{
|
||||||
|
width = Math.Max(width, childSize.Width);
|
||||||
|
height += childSize.Height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
width += childSize.Width;
|
||||||
|
height = Math.Max(height, childSize.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Size((int) width, (int) height);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||||
|
{
|
||||||
|
var neededRerender = false;
|
||||||
|
IReadOnlyList<IView> forceRerenderChildren;
|
||||||
|
lock (_forceRerenderChildrenLock)
|
||||||
|
{
|
||||||
|
forceRerenderChildren = _forceRerenderChildren.ToList();
|
||||||
|
_forceRerenderChildren.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var delta = 0;
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
if (!child.IsVisible) continue;
|
||||||
|
|
||||||
|
if (!_requestedSizes.TryGetValue(child, out var childSize)) throw new Exception("Child size not found");
|
||||||
|
|
||||||
|
|
||||||
|
var childPosition = Orientation == Orientation.Vertical
|
||||||
|
? position with {Y = position.Y + delta}
|
||||||
|
: position with {X = position.X + delta};
|
||||||
|
|
||||||
|
childSize = Orientation == Orientation.Vertical
|
||||||
|
? childSize with {Width = size.Width}
|
||||||
|
: childSize with {Height = size.Height};
|
||||||
|
|
||||||
|
var endX = position.X + size.Width;
|
||||||
|
var endY = position.Y + size.Height;
|
||||||
|
|
||||||
|
if (childPosition.X > endX || childPosition.Y > endY) break;
|
||||||
|
if (childPosition.X + childSize.Width > endX)
|
||||||
|
{
|
||||||
|
childSize = childSize with {Width = endX - childPosition.X};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childPosition.Y + childSize.Height > endY)
|
||||||
|
{
|
||||||
|
childSize = childSize with {Height = endY - childPosition.Y};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceRerenderChildren.Contains(child))
|
||||||
|
{
|
||||||
|
var rerenderContext = new RenderContext(
|
||||||
|
renderContext.ConsoleDriver,
|
||||||
|
true,
|
||||||
|
renderContext.Foreground,
|
||||||
|
renderContext.Background,
|
||||||
|
renderContext.Statistics
|
||||||
|
);
|
||||||
|
neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
neededRerender = child.Render(renderContext, childPosition, childSize) || neededRerender;
|
||||||
|
}
|
||||||
|
|
||||||
|
delta += Orientation == Orientation.Vertical
|
||||||
|
? childSize.Height
|
||||||
|
: childSize.Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return neededRerender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IView<TItem>? CreateItem(TItem dataContext)
|
||||||
|
{
|
||||||
|
var newItem = ItemTemplate();
|
||||||
|
AddChild(newItem, _ => dataContext);
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IView<TItem>? DefaultItemTemplate() => null;
|
||||||
|
|
||||||
|
public void ChildVisibilityChanged(IView child)
|
||||||
|
{
|
||||||
|
var viewToForceRerender = child;
|
||||||
|
while (viewToForceRerender.VisualParent != null && viewToForceRerender.VisualParent != this)
|
||||||
|
{
|
||||||
|
viewToForceRerender = viewToForceRerender.VisualParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewToForceRerender.VisualParent != this) return;
|
||||||
|
|
||||||
|
lock (_forceRerenderChildrenLock)
|
||||||
|
{
|
||||||
|
_forceRerenderChildren.Add(viewToForceRerender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ using TerminalUI.Models;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
public sealed partial class ListView<TDataContext, TItem> : View<ListView<TDataContext, TItem>, TDataContext>
|
||||||
{
|
{
|
||||||
private static readonly ArrayPool<ListViewItem<TItem, TDataContext>> ListViewItemPool = ArrayPool<ListViewItem<TItem, TDataContext>>.Shared;
|
private static readonly ArrayPool<ListViewItem<TItem, TDataContext>> ListViewItemPool = ArrayPool<ListViewItem<TItem, TDataContext>>.Shared;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using TerminalUI.Models;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public partial class ListViewItem<T, TParentDataContext> : ContentView<T>
|
public sealed partial class ListViewItem<T, TParentDataContext> : ContentView<ListViewItem<T, TParentDataContext>, T>
|
||||||
{
|
{
|
||||||
public ListView<TParentDataContext, T> Parent { get; }
|
public ListView<TParentDataContext, T> Parent { get; }
|
||||||
[Notify] private bool _isSelected;
|
[Notify] private bool _isSelected;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public partial class Rectangle<T> : View<T>, IDisplayView
|
public sealed partial class Rectangle<T> : View<Rectangle<T>, T>, IDisplayView
|
||||||
{
|
{
|
||||||
private record RenderState(
|
private record RenderState(
|
||||||
Position Position,
|
Position Position,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public partial class StackPanel<T> : ChildContainerView<T>, IVisibilityChangeHandler
|
public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>, IVisibilityChangeHandler
|
||||||
{
|
{
|
||||||
private readonly List<IView> _forceRerenderChildren = new();
|
private readonly List<IView> _forceRerenderChildren = new();
|
||||||
private readonly object _forceRerenderChildrenLock = new();
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
@@ -41,7 +41,6 @@ public partial class StackPanel<T> : ChildContainerView<T>, IVisibilityChangeHan
|
|||||||
|
|
||||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
var delta = 0;
|
|
||||||
var neededRerender = false;
|
var neededRerender = false;
|
||||||
IReadOnlyList<IView> forceRerenderChildren;
|
IReadOnlyList<IView> forceRerenderChildren;
|
||||||
lock (_forceRerenderChildrenLock)
|
lock (_forceRerenderChildrenLock)
|
||||||
@@ -50,17 +49,21 @@ public partial class StackPanel<T> : ChildContainerView<T>, IVisibilityChangeHan
|
|||||||
_forceRerenderChildren.Clear();
|
_forceRerenderChildren.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var delta = 0;
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
if (!child.IsVisible) continue;
|
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)) throw new Exception("Child size not found");
|
||||||
|
|
||||||
|
|
||||||
var childPosition = Orientation == Orientation.Vertical
|
var childPosition = Orientation == Orientation.Vertical
|
||||||
? position with {Y = position.Y + delta}
|
? position with {Y = position.Y + delta}
|
||||||
: position with {X = position.X + delta};
|
: position with {X = position.X + delta};
|
||||||
|
|
||||||
|
childSize = Orientation == Orientation.Vertical
|
||||||
|
? childSize with {Width = size.Width}
|
||||||
|
: childSize with {Height = size.Height};
|
||||||
|
|
||||||
var endX = position.X + size.Width;
|
var endX = position.X + size.Width;
|
||||||
var endY = position.Y + size.Height;
|
var endY = position.Y + size.Height;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using TerminalUI.Traits;
|
|||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
[DebuggerDisplay("Text = {Text}")]
|
[DebuggerDisplay("Text = {Text}")]
|
||||||
public partial class TextBlock<T> : View<T>, IDisplayView
|
public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
||||||
{
|
{
|
||||||
private record RenderState(
|
private record RenderState(
|
||||||
Position Position,
|
Position Position,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using TerminalUI.Traits;
|
|||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
[DebuggerDisplay("Text = {Text}")]
|
[DebuggerDisplay("Text = {Text}")]
|
||||||
public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDisplayView
|
||||||
{
|
{
|
||||||
private record RenderState(
|
private record RenderState(
|
||||||
string? Text,
|
string? Text,
|
||||||
@@ -20,7 +20,6 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
IColor? BackgroundColor
|
IColor? BackgroundColor
|
||||||
);
|
);
|
||||||
|
|
||||||
private readonly List<Action<TextBox<T>, GeneralKeyEventArgs>> _keyHandlers = new();
|
|
||||||
private readonly List<Action<TextBox<T>, string>> _textHandlers = new();
|
private readonly List<Action<TextBox<T>, string>> _textHandlers = new();
|
||||||
|
|
||||||
private RenderState? _lastRenderState;
|
private RenderState? _lastRenderState;
|
||||||
@@ -31,6 +30,7 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
private Position _relativeCursorPosition = new(0, 0);
|
private Position _relativeCursorPosition = new(0, 0);
|
||||||
|
|
||||||
[Notify] private bool _multiLine;
|
[Notify] private bool _multiLine;
|
||||||
|
[Notify] private char? _passwordChar;
|
||||||
public bool SetKeyHandledIfKnown { get; set; }
|
public bool SetKeyHandledIfKnown { get; set; }
|
||||||
|
|
||||||
public string Text
|
public string Text
|
||||||
@@ -55,6 +55,7 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
_textLines = _text.Split(Environment.NewLine).ToList();
|
_textLines = _text.Split(Environment.NewLine).ToList();
|
||||||
RerenderProperties.Add(nameof(Text));
|
RerenderProperties.Add(nameof(Text));
|
||||||
RerenderProperties.Add(nameof(MultiLine));
|
RerenderProperties.Add(nameof(MultiLine));
|
||||||
|
RerenderProperties.Add(nameof(PasswordChar));
|
||||||
|
|
||||||
((INotifyPropertyChanged) this).PropertyChanged += OnPropertyChangedEventHandler;
|
((INotifyPropertyChanged) this).PropertyChanged += OnPropertyChangedEventHandler;
|
||||||
}
|
}
|
||||||
@@ -115,12 +116,41 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
}
|
}
|
||||||
|
|
||||||
RenderEmpty(renderContext, position, size);
|
RenderEmpty(renderContext, position, size);
|
||||||
|
|
||||||
|
if (PasswordChar is { } passwordChar && !char.IsControl(passwordChar))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _textLines.Count; i++)
|
||||||
|
{
|
||||||
|
var pos = position with {Y = position.Y + i};
|
||||||
|
RenderPasswordTextLine(_textLines[i], passwordChar, driver, pos, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
RenderText(_textLines, driver, position, size);
|
RenderText(_textLines, driver, position, size);
|
||||||
|
}
|
||||||
|
|
||||||
_cursorPosition = position + _relativeCursorPosition;
|
_cursorPosition = position + _relativeCursorPosition;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RenderPasswordTextLine(
|
||||||
|
string sourceText,
|
||||||
|
char passwordChar,
|
||||||
|
IConsoleDriver driver,
|
||||||
|
Position position,
|
||||||
|
Size size)
|
||||||
|
{
|
||||||
|
Span<char> text = stackalloc char[sourceText.Length];
|
||||||
|
for (var j = 0; j < text.Length; j++)
|
||||||
|
{
|
||||||
|
text[j] = passwordChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderText(text, driver, position, size);
|
||||||
|
}
|
||||||
|
|
||||||
private bool NeedsRerender(RenderState renderState)
|
private bool NeedsRerender(RenderState renderState)
|
||||||
=> _lastRenderState is null || _lastRenderState != renderState;
|
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||||
|
|
||||||
@@ -136,13 +166,22 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
consoleDriver.SetCursorPosition(_cursorPosition.Value);
|
consoleDriver.SetCursorPosition(_cursorPosition.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleKeyInput(GeneralKeyEventArgs keyEventArgs)
|
public override void HandleKeyInput(GeneralKeyEventArgs keyEventArgs)
|
||||||
{
|
{
|
||||||
HandleKeyInputInternal(keyEventArgs);
|
HandleKeyInputInternal(keyEventArgs);
|
||||||
if (keyEventArgs.Handled)
|
if (keyEventArgs.Handled)
|
||||||
{
|
{
|
||||||
ApplicationContext?.RenderEngine.RequestRerender(this);
|
ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var view = VisualParent;
|
||||||
|
while (view != null && !keyEventArgs.Handled)
|
||||||
|
{
|
||||||
|
view.HandleKeyInput(keyEventArgs);
|
||||||
|
view = view.VisualParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleKeyInputInternal(GeneralKeyEventArgs keyEventArgs)
|
private void HandleKeyInputInternal(GeneralKeyEventArgs keyEventArgs)
|
||||||
@@ -158,11 +197,14 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
if (!known && HandleNavigation(keyEventArgs, out known))
|
if (!known && HandleNavigation(keyEventArgs, out known))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!known && ProcessKeyHandlers(keyEventArgs))
|
if (!known)
|
||||||
return;
|
{
|
||||||
|
ProcessKeyHandlers(keyEventArgs);
|
||||||
|
if (keyEventArgs.Handled) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!known
|
if (!known
|
||||||
&& keyEventArgs.KeyChar != '\0'
|
&& !char.IsControl(keyEventArgs.KeyChar)
|
||||||
&& keyEventArgs.KeyChar.ToString() is {Length: 1} keyString)
|
&& keyEventArgs.KeyChar.ToString() is {Length: 1} keyString)
|
||||||
{
|
{
|
||||||
var y = _relativeCursorPosition.Y;
|
var y = _relativeCursorPosition.Y;
|
||||||
@@ -172,6 +214,12 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
|
|
||||||
keyEventArgs.Handled = true;
|
keyEventArgs.Handled = true;
|
||||||
UpdateTextField();
|
UpdateTextField();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!known)
|
||||||
|
{
|
||||||
|
ProcessParentKeyHandlers(keyEventArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,29 +342,6 @@ public partial class TextBox<T> : View<T>, IFocusable, IDisplayView
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ProcessKeyHandlers(GeneralKeyEventArgs keyEventArgs)
|
|
||||||
{
|
|
||||||
foreach (var keyHandler in _keyHandlers)
|
|
||||||
{
|
|
||||||
keyHandler(this, keyEventArgs);
|
|
||||||
if (keyEventArgs.Handled) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
_keyHandlers.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextBox<T> WithKeyHandler(Action<TextBox<T>, GeneralKeyEventArgs> keyHandler)
|
|
||||||
{
|
|
||||||
_keyHandlers.Add(keyHandler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextBox<T> WithTextHandler(Action<TextBox<T>, string> textChanged)
|
public TextBox<T> WithTextHandler(Action<TextBox<T>, string> textChanged)
|
||||||
{
|
{
|
||||||
_textHandlers.Add(textChanged);
|
_textHandlers.Add(textChanged);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using GeneralInputKey;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.ConsoleDrivers;
|
using TerminalUI.ConsoleDrivers;
|
||||||
@@ -12,9 +13,10 @@ namespace TerminalUI.Controls;
|
|||||||
|
|
||||||
public delegate string TextTransformer(string text, Position position, Size size);
|
public delegate string TextTransformer(string text, Position position, Size size);
|
||||||
|
|
||||||
public abstract partial class View<T> : IView<T>
|
public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : View<TConcrete, T>
|
||||||
{
|
{
|
||||||
private readonly List<IDisposable> _disposables = new();
|
private readonly List<IDisposable> _disposables = new();
|
||||||
|
private readonly ReadOnlyObservableCollection<IView> _readOnlyVisualChildren;
|
||||||
[Notify] private T? _dataContext;
|
[Notify] private T? _dataContext;
|
||||||
[Notify] private int? _minWidth;
|
[Notify] private int? _minWidth;
|
||||||
[Notify] private int? _maxWidth;
|
[Notify] private int? _maxWidth;
|
||||||
@@ -33,7 +35,9 @@ public abstract partial class View<T> : IView<T>
|
|||||||
[Notify] private bool _attached;
|
[Notify] private bool _attached;
|
||||||
[Notify] private IView? _visualParent;
|
[Notify] private IView? _visualParent;
|
||||||
|
|
||||||
|
protected List<Action<TConcrete, GeneralKeyEventArgs>> KeyHandlers { get; } = new();
|
||||||
protected ObservableCollection<IView> VisualChildren { get; } = new();
|
protected ObservableCollection<IView> VisualChildren { get; } = new();
|
||||||
|
ReadOnlyObservableCollection<IView> IView.VisualChildren => _readOnlyVisualChildren;
|
||||||
|
|
||||||
public List<object> Extensions { get; } = new();
|
public List<object> Extensions { get; } = new();
|
||||||
public RenderMethod RenderMethod { get; set; }
|
public RenderMethod RenderMethod { get; set; }
|
||||||
@@ -43,6 +47,7 @@ public abstract partial class View<T> : IView<T>
|
|||||||
protected View()
|
protected View()
|
||||||
{
|
{
|
||||||
RenderMethod = DefaultRenderer;
|
RenderMethod = DefaultRenderer;
|
||||||
|
_readOnlyVisualChildren = new ReadOnlyObservableCollection<IView>(VisualChildren);
|
||||||
|
|
||||||
RerenderProperties.Add(nameof(Width));
|
RerenderProperties.Add(nameof(Width));
|
||||||
RerenderProperties.Add(nameof(MinWidth));
|
RerenderProperties.Add(nameof(MinWidth));
|
||||||
@@ -245,6 +250,27 @@ public abstract partial class View<T> : IView<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void RenderText(
|
||||||
|
in ReadOnlySpan<char> text,
|
||||||
|
IConsoleDriver driver,
|
||||||
|
Position position,
|
||||||
|
Size size)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < size.Height; i++)
|
||||||
|
{
|
||||||
|
var currentPosition = position with {Y = position.Y + i};
|
||||||
|
var finalText = text;
|
||||||
|
|
||||||
|
if (finalText.Length > size.Width)
|
||||||
|
{
|
||||||
|
finalText = finalText[..size.Width];
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.SetCursorPosition(currentPosition);
|
||||||
|
driver.Write(finalText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void RenderText(
|
protected void RenderText(
|
||||||
char content,
|
char content,
|
||||||
IConsoleDriver driver,
|
IConsoleDriver driver,
|
||||||
@@ -367,7 +393,39 @@ public abstract partial class View<T> : IView<T>
|
|||||||
arrayPool.Return(disposables, true);
|
arrayPool.Return(disposables, true);
|
||||||
|
|
||||||
_disposables.Clear();
|
_disposables.Clear();
|
||||||
|
KeyHandlers.Clear();
|
||||||
|
|
||||||
Disposed?.Invoke(this);
|
Disposed?.Invoke(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void HandleKeyInput(GeneralKeyEventArgs keyEventArgs)
|
||||||
|
{
|
||||||
|
ProcessKeyHandlers(keyEventArgs);
|
||||||
|
ProcessParentKeyHandlers(keyEventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void ProcessKeyHandlers(GeneralKeyEventArgs keyEventArgs)
|
||||||
|
{
|
||||||
|
foreach (var keyHandler in KeyHandlers)
|
||||||
|
{
|
||||||
|
keyHandler((TConcrete)this, keyEventArgs);
|
||||||
|
if (keyEventArgs.Handled) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ProcessParentKeyHandlers(GeneralKeyEventArgs keyEventArgs)
|
||||||
|
{
|
||||||
|
if (VisualParent is { } parent)
|
||||||
|
{
|
||||||
|
parent.HandleKeyInput(keyEventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TConcrete WithKeyHandler(Action<TConcrete, GeneralKeyEventArgs> keyHandler)
|
||||||
|
{
|
||||||
|
KeyHandlers.Add(keyHandler);
|
||||||
|
return (TConcrete)this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,21 @@ public class FocusManager : IFocusManager
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_focused is not null && !_focused.IsVisible)
|
if (_focused is not null)
|
||||||
|
{
|
||||||
|
var visible = _focused.IsVisible;
|
||||||
|
var parent = _focused.VisualParent;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
visible &= parent.IsVisible;
|
||||||
|
parent = parent.VisualParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visible)
|
||||||
{
|
{
|
||||||
_focused = null;
|
_focused = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _focused;
|
return _focused;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
<ProjectReference Include="..\DeclarativeProperty\DeclarativeProperty.csproj" />
|
<ProjectReference Include="..\DeclarativeProperty\DeclarativeProperty.csproj" />
|
||||||
|
<ProjectReference Include="..\ObservableComputations.Extensions\ObservableComputations.Extensions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user