Console upgrades, PossibleCommands VM
This commit is contained in:
@@ -14,7 +14,7 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel<ICommandPaletteEntryV
|
|||||||
private readonly ICommandPaletteService _commandPaletteService;
|
private readonly ICommandPaletteService _commandPaletteService;
|
||||||
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
||||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private readonly IKeyboardConfigurationService _keyboardConfigurationService;
|
private readonly ICommandKeysHelperService _commandKeysHelperService;
|
||||||
private readonly ILogger<CommandPaletteViewModel> _logger;
|
private readonly ILogger<CommandPaletteViewModel> _logger;
|
||||||
string IModalViewModel.Name => "CommandPalette";
|
string IModalViewModel.Name => "CommandPalette";
|
||||||
|
|
||||||
@@ -22,14 +22,14 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel<ICommandPaletteEntryV
|
|||||||
ICommandPaletteService commandPaletteService,
|
ICommandPaletteService commandPaletteService,
|
||||||
IIdentifiableUserCommandService identifiableUserCommandService,
|
IIdentifiableUserCommandService identifiableUserCommandService,
|
||||||
IUserCommandHandlerService userCommandHandlerService,
|
IUserCommandHandlerService userCommandHandlerService,
|
||||||
IKeyboardConfigurationService keyboardConfigurationService,
|
ICommandKeysHelperService commandKeysHelperService,
|
||||||
ILogger<CommandPaletteViewModel> logger)
|
ILogger<CommandPaletteViewModel> logger)
|
||||||
: base((a, b) => a.Identifier == b.Identifier)
|
: base((a, b) => a.Identifier == b.Identifier)
|
||||||
{
|
{
|
||||||
_commandPaletteService = commandPaletteService;
|
_commandPaletteService = commandPaletteService;
|
||||||
_identifiableUserCommandService = identifiableUserCommandService;
|
_identifiableUserCommandService = identifiableUserCommandService;
|
||||||
_userCommandHandlerService = userCommandHandlerService;
|
_userCommandHandlerService = userCommandHandlerService;
|
||||||
_keyboardConfigurationService = keyboardConfigurationService;
|
_commandKeysHelperService = commandKeysHelperService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
ShowWindow = _commandPaletteService.ShowWindow;
|
ShowWindow = _commandPaletteService.ShowWindow;
|
||||||
UpdateFilteredMatchesInternal();
|
UpdateFilteredMatchesInternal();
|
||||||
@@ -47,47 +47,10 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel<ICommandPaletteEntryV
|
|||||||
|| c.Identifier.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
|
|| c.Identifier.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
|
||||||
)
|
)
|
||||||
.Select(c =>
|
.Select(c =>
|
||||||
(ICommandPaletteEntryViewModel) new CommandPaletteEntryViewModel(c.Identifier, c.Title, GetKeyConfigsString(c.Identifier))
|
(ICommandPaletteEntryViewModel) new CommandPaletteEntryViewModel(c.Identifier, c.Title, _commandKeysHelperService.GetKeyConfigsString(c.Identifier))
|
||||||
)
|
)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
private string GetKeyConfigsString(string commandIdentifier)
|
|
||||||
{
|
|
||||||
var keyConfigs = GetKeyConfigsForCommand(commandIdentifier);
|
|
||||||
if (keyConfigs.Count == 0) return string.Empty;
|
|
||||||
|
|
||||||
return string.Join(
|
|
||||||
" ; ",
|
|
||||||
keyConfigs
|
|
||||||
.Select(ks =>
|
|
||||||
string.Join(
|
|
||||||
", ",
|
|
||||||
ks.Select(FormatKeyConfig)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatKeyConfig(KeyConfig keyConfig)
|
|
||||||
{
|
|
||||||
var stringBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
if (keyConfig.Ctrl) stringBuilder.Append("Ctrl + ");
|
|
||||||
if (keyConfig.Shift) stringBuilder.Append("Shift + ");
|
|
||||||
if (keyConfig.Alt) stringBuilder.Append("Alt + ");
|
|
||||||
|
|
||||||
stringBuilder.Append(keyConfig.Key.ToString());
|
|
||||||
|
|
||||||
return stringBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<List<KeyConfig>> GetKeyConfigsForCommand(string commandIdentifier)
|
|
||||||
=> _keyboardConfigurationService
|
|
||||||
.AllShortcut
|
|
||||||
.Where(s => s.Command == commandIdentifier)
|
|
||||||
.Select(k => k.Keys)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
public override async Task<bool> HandleKeyDown(GeneralKeyEventArgs keyEventArgs)
|
public override async Task<bool> HandleKeyDown(GeneralKeyEventArgs keyEventArgs)
|
||||||
{
|
{
|
||||||
if (keyEventArgs.Handled) return false;
|
if (keyEventArgs.Handled) return false;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using FileTime.App.Core.Configuration;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public interface ICommandKeysHelperService
|
||||||
|
{
|
||||||
|
List<List<KeyConfig>> GetKeysForCommand(string commandName);
|
||||||
|
string GetKeyConfigsString(string commandIdentifier);
|
||||||
|
string FormatKeyConfig(KeyConfig keyConfig);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.App.Core.Configuration;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public interface IPossibleCommandsService
|
||||||
|
{
|
||||||
|
ReadOnlyObservableCollection<CommandBindingConfiguration> PossibleCommands { get; set; }
|
||||||
|
void Clear();
|
||||||
|
void Add(CommandBindingConfiguration commandBindingConfiguration);
|
||||||
|
void Remove(CommandBindingConfiguration commandBindingConfiguration);
|
||||||
|
void AddRange(IEnumerable<CommandBindingConfiguration> commandBindingConfigurations);
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ public interface IAppState
|
|||||||
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
||||||
IDeclarativeProperty<string?> ContainerStatus { get; }
|
IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||||
List<KeyConfig> PreviousKeys { get; }
|
List<KeyConfig> PreviousKeys { get; }
|
||||||
List<CommandBindingConfiguration> PossibleCommands { get; set; }
|
|
||||||
bool NoCommandFound { get; set; }
|
bool NoCommandFound { get; set; }
|
||||||
|
|
||||||
void AddTab(ITabViewModel tabViewModel);
|
void AddTab(ITabViewModel tabViewModel);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
public interface IPossibleCommandEntryViewModel
|
||||||
|
{
|
||||||
|
public string CommandName { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
public string KeysText { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
public interface IPossibleCommandsViewModel
|
||||||
|
{
|
||||||
|
ObservableCollection<IPossibleCommandEntryViewModel> PossibleCommands { get; }
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
||||||
<ProjectReference Include="..\..\Library\Defer\Defer.csproj" />
|
<ProjectReference Include="..\..\Library\Defer\Defer.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Library\ObservableComputations.Extensions\ObservableComputations.Extensions.csproj" />
|
||||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
|
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.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" />
|
||||||
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
|
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text;
|
||||||
|
using FileTime.App.Core.Configuration;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public class CommandKeysHelperService : ICommandKeysHelperService
|
||||||
|
{
|
||||||
|
private readonly IKeyboardConfigurationService _keyboardConfigurationService;
|
||||||
|
|
||||||
|
public CommandKeysHelperService(IKeyboardConfigurationService keyboardConfigurationService)
|
||||||
|
{
|
||||||
|
_keyboardConfigurationService = keyboardConfigurationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<List<KeyConfig>> GetKeysForCommand(string commandName)
|
||||||
|
=> _keyboardConfigurationService
|
||||||
|
.AllShortcut
|
||||||
|
.Where(s => s.Command == commandName)
|
||||||
|
.Select(k => k.Keys)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
public string GetKeyConfigsString(string commandIdentifier)
|
||||||
|
{
|
||||||
|
var keyConfigs = GetKeysForCommand(commandIdentifier);
|
||||||
|
if (keyConfigs.Count == 0) return string.Empty;
|
||||||
|
|
||||||
|
return string.Join(
|
||||||
|
" ; ",
|
||||||
|
keyConfigs
|
||||||
|
.Select(ks =>
|
||||||
|
string.Join(
|
||||||
|
", ",
|
||||||
|
ks.Select(FormatKeyConfig)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatKeyConfig(KeyConfig keyConfig)
|
||||||
|
{
|
||||||
|
var stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
if (keyConfig.Ctrl) stringBuilder.Append("Ctrl + ");
|
||||||
|
if (keyConfig.Shift) stringBuilder.Append("Shift + ");
|
||||||
|
if (keyConfig.Alt) stringBuilder.Append("Alt + ");
|
||||||
|
|
||||||
|
stringBuilder.Append(keyConfig.Key.ToString());
|
||||||
|
|
||||||
|
return stringBuilder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
private readonly ILogger<DefaultModeKeyInputHandler> _logger;
|
private readonly ILogger<DefaultModeKeyInputHandler> _logger;
|
||||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
||||||
|
private readonly IPossibleCommandsService _possibleCommandsService;
|
||||||
private readonly BindedCollection<IModalViewModel> _openModals;
|
private readonly BindedCollection<IModalViewModel> _openModals;
|
||||||
|
|
||||||
public DefaultModeKeyInputHandler(
|
public DefaultModeKeyInputHandler(
|
||||||
@@ -29,10 +30,12 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
IKeyboardConfigurationService keyboardConfigurationService,
|
IKeyboardConfigurationService keyboardConfigurationService,
|
||||||
ILogger<DefaultModeKeyInputHandler> logger,
|
ILogger<DefaultModeKeyInputHandler> logger,
|
||||||
IUserCommandHandlerService userCommandHandlerService,
|
IUserCommandHandlerService userCommandHandlerService,
|
||||||
IIdentifiableUserCommandService identifiableUserCommandService)
|
IIdentifiableUserCommandService identifiableUserCommandService,
|
||||||
|
IPossibleCommandsService possibleCommandsService)
|
||||||
{
|
{
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
_identifiableUserCommandService = identifiableUserCommandService;
|
_identifiableUserCommandService = identifiableUserCommandService;
|
||||||
|
_possibleCommandsService = possibleCommandsService;
|
||||||
_keyboardConfigurationService = keyboardConfigurationService;
|
_keyboardConfigurationService = keyboardConfigurationService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_modalService = modalService;
|
_modalService = modalService;
|
||||||
@@ -99,7 +102,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
_appState.PreviousKeys.Clear();
|
_appState.PreviousKeys.Clear();
|
||||||
_appState.PossibleCommands = new();
|
_possibleCommandsService.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*else if (key == Key.Enter
|
/*else if (key == Key.Enter
|
||||||
@@ -113,7 +116,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
_appState.PreviousKeys.Clear();
|
_appState.PreviousKeys.Clear();
|
||||||
_appState.PossibleCommands = new();
|
_possibleCommandsService.Clear();
|
||||||
var command = _identifiableUserCommandService.GetCommand(selectedCommandBinding.Command);
|
var command = _identifiableUserCommandService.GetCommand(selectedCommandBinding.Command);
|
||||||
if (command is not null)
|
if (command is not null)
|
||||||
{
|
{
|
||||||
@@ -123,7 +126,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
else if (_keysToSkip.Any(k => k.AreKeysEqual(_appState.PreviousKeys)))
|
else if (_keysToSkip.Any(k => k.AreKeysEqual(_appState.PreviousKeys)))
|
||||||
{
|
{
|
||||||
_appState.PreviousKeys.Clear();
|
_appState.PreviousKeys.Clear();
|
||||||
_appState.PossibleCommands = new();
|
_possibleCommandsService.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (_appState.PreviousKeys.Count == 2)
|
else if (_appState.PreviousKeys.Count == 2)
|
||||||
@@ -131,7 +134,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
_appState.NoCommandFound = true;
|
_appState.NoCommandFound = true;
|
||||||
_appState.PreviousKeys.Clear();
|
_appState.PreviousKeys.Clear();
|
||||||
_appState.PossibleCommands = new();
|
_possibleCommandsService.Clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -145,7 +148,8 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_appState.PossibleCommands = possibleCommands;
|
_possibleCommandsService.Clear();
|
||||||
|
_possibleCommandsService.AddRange(possibleCommands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.App.Core.Configuration;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Services;
|
||||||
|
|
||||||
|
public class PossibleCommandsService : IPossibleCommandsService
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<CommandBindingConfiguration> _possibleCommands = new();
|
||||||
|
public ReadOnlyObservableCollection<CommandBindingConfiguration> PossibleCommands { get; set; }
|
||||||
|
|
||||||
|
public PossibleCommandsService()
|
||||||
|
{
|
||||||
|
PossibleCommands = new ReadOnlyObservableCollection<CommandBindingConfiguration>(_possibleCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _possibleCommands.Clear();
|
||||||
|
|
||||||
|
public void Add(CommandBindingConfiguration commandBindingConfiguration)
|
||||||
|
=> _possibleCommands.Add(commandBindingConfiguration);
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<CommandBindingConfiguration> commandBindingConfigurations)
|
||||||
|
{
|
||||||
|
foreach (var commandBindingConfiguration in commandBindingConfigurations)
|
||||||
|
{
|
||||||
|
_possibleCommands.Add(commandBindingConfiguration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(CommandBindingConfiguration commandBindingConfiguration)
|
||||||
|
=> _possibleCommands.Remove(commandBindingConfiguration);
|
||||||
|
}
|
||||||
@@ -33,6 +33,9 @@ public static class Startup
|
|||||||
serviceCollection.TryAddSingleton<IDefaultModeKeyInputHandler, DefaultModeKeyInputHandler>();
|
serviceCollection.TryAddSingleton<IDefaultModeKeyInputHandler, DefaultModeKeyInputHandler>();
|
||||||
serviceCollection.TryAddSingleton<IRapidTravelModeKeyInputHandler, RapidTravelModeKeyInputHandler>();
|
serviceCollection.TryAddSingleton<IRapidTravelModeKeyInputHandler, RapidTravelModeKeyInputHandler>();
|
||||||
serviceCollection.TryAddSingleton<IKeyboardConfigurationService, KeyboardConfigurationService>();
|
serviceCollection.TryAddSingleton<IKeyboardConfigurationService, KeyboardConfigurationService>();
|
||||||
|
serviceCollection.TryAddSingleton<ICommandKeysHelperService, CommandKeysHelperService>();
|
||||||
|
serviceCollection.TryAddSingleton<IPossibleCommandsService, PossibleCommandsService>();
|
||||||
|
serviceCollection.TryAddSingleton<IPossibleCommandsViewModel, PossibleCommandsViewModel>();
|
||||||
|
|
||||||
return serviceCollection
|
return serviceCollection
|
||||||
.AddCommandHandlers()
|
.AddCommandHandlers()
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ using System.Reactive.Subjects;
|
|||||||
using DeclarativeProperty;
|
using DeclarativeProperty;
|
||||||
using FileTime.App.Core.Configuration;
|
using FileTime.App.Core.Configuration;
|
||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.ViewModels.Timeline;
|
|
||||||
using FileTime.Core.Models.Extensions;
|
using FileTime.Core.Models.Extensions;
|
||||||
using MvvmGen;
|
|
||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
@@ -31,7 +29,6 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
|
|
||||||
public IDeclarativeProperty<string?> ContainerStatus { get; }
|
public IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||||
[Notify] public List<KeyConfig> PreviousKeys { get; } = new();
|
[Notify] public List<KeyConfig> PreviousKeys { get; } = new();
|
||||||
[Notify] public List<CommandBindingConfiguration> PossibleCommands { get; set; } = new();
|
|
||||||
[Notify] public bool NoCommandFound { get; set; }
|
[Notify] public bool NoCommandFound { get; set; }
|
||||||
|
|
||||||
protected AppStateBase()
|
protected AppStateBase()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
public record PossibleCommandEntryViewModel(
|
||||||
|
string CommandName,
|
||||||
|
string Title,
|
||||||
|
string KeysText) : IPossibleCommandEntryViewModel;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.App.Core.Configuration;
|
||||||
|
using FileTime.App.Core.Services;
|
||||||
|
using ObservableComputations;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
public class PossibleCommandsViewModel : IPossibleCommandsViewModel, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IIdentifiableUserCommandService _identifiableUserCommandService;
|
||||||
|
private readonly OcConsumer _ocConsumer = new();
|
||||||
|
public ObservableCollection<IPossibleCommandEntryViewModel> PossibleCommands { get; }
|
||||||
|
|
||||||
|
public PossibleCommandsViewModel(
|
||||||
|
IPossibleCommandsService possibleCommandsService,
|
||||||
|
IIdentifiableUserCommandService identifiableUserCommandService)
|
||||||
|
{
|
||||||
|
_identifiableUserCommandService = identifiableUserCommandService;
|
||||||
|
PossibleCommands = possibleCommandsService
|
||||||
|
.PossibleCommands
|
||||||
|
.Selecting(c => CreatePossibleCommandViewModel(c))
|
||||||
|
.For(_ocConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPossibleCommandEntryViewModel CreatePossibleCommandViewModel(CommandBindingConfiguration commandBindingConfiguration)
|
||||||
|
{
|
||||||
|
var commandName = commandBindingConfiguration.Command;
|
||||||
|
var title = _identifiableUserCommandService.GetCommand(commandName)?.Title ?? commandName;
|
||||||
|
return new PossibleCommandEntryViewModel(
|
||||||
|
CommandName: commandName,
|
||||||
|
Title: title,
|
||||||
|
KeysText: commandBindingConfiguration.GetKeysDisplayText());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => _ocConsumer.Dispose();
|
||||||
|
}
|
||||||
@@ -4,5 +4,4 @@ namespace FileTime.ConsoleUI.App;
|
|||||||
|
|
||||||
public interface IConsoleAppState : IAppState
|
public interface IConsoleAppState : IAppState
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
|
public interface IRootViewModel
|
||||||
|
{
|
||||||
|
IConsoleAppState AppState { get; }
|
||||||
|
IPossibleCommandsViewModel PossibleCommands { get; }
|
||||||
|
string UserName { get; }
|
||||||
|
string MachineName { get; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using DeclarativeProperty;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.Models.Enums;
|
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
@@ -8,69 +7,103 @@ using TerminalUI.Controls;
|
|||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
using TerminalUI.ViewExtensions;
|
using TerminalUI.ViewExtensions;
|
||||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
public class MainWindow
|
public class MainWindow
|
||||||
{
|
{
|
||||||
private readonly IConsoleAppState _consoleAppState;
|
private readonly IRootViewModel _rootViewModel;
|
||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly ITheme _theme;
|
private readonly ITheme _theme;
|
||||||
|
|
||||||
private IView _root;
|
private IView _root;
|
||||||
|
|
||||||
public MainWindow(
|
public MainWindow(
|
||||||
IConsoleAppState consoleAppState,
|
IRootViewModel rootViewModel,
|
||||||
IApplicationContext applicationContext,
|
IApplicationContext applicationContext,
|
||||||
ITheme theme)
|
ITheme theme)
|
||||||
{
|
{
|
||||||
_consoleAppState = consoleAppState;
|
_rootViewModel = rootViewModel;
|
||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
_theme = theme;
|
_theme = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
var root = new Grid<IAppState>
|
var root = new Grid<IRootViewModel>
|
||||||
{
|
{
|
||||||
DataContext = _consoleAppState,
|
DataContext = _rootViewModel,
|
||||||
ApplicationContext = _applicationContext,
|
ApplicationContext = _applicationContext,
|
||||||
RowDefinitionsObject = "Auto *",
|
RowDefinitionsObject = "Auto * Auto",
|
||||||
ChildInitializer =
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
new Grid<IAppState>
|
new Grid<IRootViewModel>
|
||||||
{
|
{
|
||||||
ColumnDefinitionsObject = "* Auto",
|
ColumnDefinitionsObject = "Auto * Auto",
|
||||||
ChildInitializer =
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
new TextBlock<IAppState>()
|
new StackPanel<IRootViewModel>
|
||||||
|
{
|
||||||
|
Name = "username_panel",
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Margin = "0 0 1 0",
|
||||||
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
Foreground = _theme.ContainerColor
|
new TextBlock<IRootViewModel>()
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
root => root.UserName,
|
||||||
|
tb => tb.Text
|
||||||
|
)),
|
||||||
|
new TextBlock<IRootViewModel>()
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
root => root.MachineName,
|
||||||
|
tb => tb.Text,
|
||||||
|
t => $"@{t}"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
.Setup(t =>
|
},
|
||||||
t.Bind(
|
new TextBlock<IRootViewModel>
|
||||||
t,
|
{
|
||||||
appState => appState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
|
Foreground = _theme.ContainerColor,
|
||||||
tb => tb.Text,
|
Extensions =
|
||||||
value => value
|
{
|
||||||
)
|
new GridPositionExtension(1, 0)
|
||||||
),
|
}
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
root => root.AppState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
|
||||||
|
tb => tb.Text
|
||||||
|
)),
|
||||||
TabControl()
|
TabControl()
|
||||||
|
.WithExtension(new GridPositionExtension(2, 0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Grid<IAppState>
|
new Grid<IRootViewModel>
|
||||||
{
|
{
|
||||||
ColumnDefinitionsObject = "* 4* 4*",
|
ColumnDefinitionsObject = "* 4* 4*",
|
||||||
ChildInitializer =
|
|
||||||
{
|
|
||||||
ParentsItemsView(),
|
|
||||||
SelectedItemsView(),
|
|
||||||
SelectedsItemsView(),
|
|
||||||
},
|
|
||||||
Extensions =
|
Extensions =
|
||||||
{
|
{
|
||||||
new GridPositionExtension(0, 1)
|
new GridPositionExtension(0, 1)
|
||||||
|
},
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
ParentsItemsView().WithExtension(new GridPositionExtension(0, 0)),
|
||||||
|
SelectedItemsView().WithExtension(new GridPositionExtension(1, 0)),
|
||||||
|
SelectedsItemsView().WithExtension(new GridPositionExtension(2, 0)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Grid<IRootViewModel>
|
||||||
|
{
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(0, 2)
|
||||||
|
},
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
PossibleCommands()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,15 +111,58 @@ public class MainWindow
|
|||||||
_root = root;
|
_root = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IView<IAppState> TabControl()
|
private IView<IRootViewModel> PossibleCommands()
|
||||||
{
|
{
|
||||||
var tabList = new ListView<IAppState, ITabViewModel>
|
//TODO: Create and use DataGrid
|
||||||
|
var commandBindings = new ListView<IRootViewModel, IPossibleCommandEntryViewModel>
|
||||||
|
{
|
||||||
|
ItemTemplate = _ =>
|
||||||
|
{
|
||||||
|
var grid = new Grid<IPossibleCommandEntryViewModel>
|
||||||
|
{
|
||||||
|
ColumnDefinitionsObject = "10 *",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new TextBlock<IPossibleCommandEntryViewModel>()
|
||||||
|
.Setup(t =>
|
||||||
|
t.Bind(
|
||||||
|
t,
|
||||||
|
dc => dc.KeysText,
|
||||||
|
tb => tb.Text)
|
||||||
|
),
|
||||||
|
new TextBlock<IPossibleCommandEntryViewModel>
|
||||||
|
{
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(1, 0)
|
||||||
|
}
|
||||||
|
}.Setup(t =>
|
||||||
|
t.Bind(
|
||||||
|
t,
|
||||||
|
dc => dc.Title,
|
||||||
|
tb => tb.Text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commandBindings.Bind(
|
||||||
|
commandBindings,
|
||||||
|
root => root.PossibleCommands.PossibleCommands,
|
||||||
|
v => v.ItemsSource,
|
||||||
|
d => d);
|
||||||
|
|
||||||
|
return commandBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IView<IRootViewModel> TabControl()
|
||||||
|
{
|
||||||
|
var tabList = new ListView<IRootViewModel, ITabViewModel>
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
Orientation = Orientation.Horizontal,
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(1, 0)
|
|
||||||
},
|
|
||||||
ItemTemplate = item =>
|
ItemTemplate = item =>
|
||||||
{
|
{
|
||||||
var textBlock = item.CreateChild<TextBlock<ITabViewModel>>();
|
var textBlock = item.CreateChild<TextBlock<ITabViewModel>>();
|
||||||
@@ -96,6 +172,7 @@ public class MainWindow
|
|||||||
textBlock,
|
textBlock,
|
||||||
dc => dc.TabNumber.ToString(),
|
dc => dc.TabNumber.ToString(),
|
||||||
tb => tb.Text,
|
tb => tb.Text,
|
||||||
|
value => $" {value}",
|
||||||
fallbackValue: "?");
|
fallbackValue: "?");
|
||||||
|
|
||||||
textBlock.Bind(
|
textBlock.Bind(
|
||||||
@@ -110,23 +187,17 @@ public class MainWindow
|
|||||||
|
|
||||||
tabList.Bind(
|
tabList.Bind(
|
||||||
tabList,
|
tabList,
|
||||||
appState => appState == null ? null : appState.Tabs,
|
root => root.AppState.Tabs,
|
||||||
v => v.ItemsSource);
|
v => v.ItemsSource);
|
||||||
|
|
||||||
return tabList;
|
return tabList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListView<IAppState, IItemViewModel> SelectedItemsView()
|
private ListView<IRootViewModel, IItemViewModel> SelectedItemsView()
|
||||||
{
|
{
|
||||||
var list = new ListView<IAppState, IItemViewModel>
|
var list = new ListView<IRootViewModel, IItemViewModel>
|
||||||
{
|
{
|
||||||
DataContext = _consoleAppState,
|
ListPadding = 8
|
||||||
ApplicationContext = _applicationContext,
|
|
||||||
ListPadding = 8,
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(1, 0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
list.ItemTemplate = item =>
|
list.ItemTemplate = item =>
|
||||||
@@ -153,33 +224,22 @@ public class MainWindow
|
|||||||
|
|
||||||
list.Bind(
|
list.Bind(
|
||||||
list,
|
list,
|
||||||
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.CurrentItems).Switch(),
|
root => root.AppState.SelectedTab.Value.CurrentItems.Value,
|
||||||
v => v.ItemsSource);
|
v => v.ItemsSource);
|
||||||
|
|
||||||
list.Bind(
|
list.Bind(
|
||||||
list,
|
list,
|
||||||
appState =>
|
root => root.AppState.SelectedTab.Value.CurrentSelectedItem.Value,
|
||||||
appState == null
|
|
||||||
? null
|
|
||||||
: appState.SelectedTab.Value == null
|
|
||||||
? null
|
|
||||||
: appState.SelectedTab.Value.CurrentSelectedItem.Value,
|
|
||||||
v => v.SelectedItem);
|
v => v.SelectedItem);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListView<IAppState, IItemViewModel> SelectedsItemsView()
|
private ListView<IRootViewModel, IItemViewModel> SelectedsItemsView()
|
||||||
{
|
{
|
||||||
var list = new ListView<IAppState, IItemViewModel>
|
var list = new ListView<IRootViewModel, IItemViewModel>
|
||||||
{
|
{
|
||||||
DataContext = _consoleAppState,
|
ListPadding = 8
|
||||||
ApplicationContext = _applicationContext,
|
|
||||||
ListPadding = 8,
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(2, 0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
list.ItemTemplate = item =>
|
list.ItemTemplate = item =>
|
||||||
@@ -206,23 +266,17 @@ public class MainWindow
|
|||||||
|
|
||||||
list.Bind(
|
list.Bind(
|
||||||
list,
|
list,
|
||||||
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.SelectedsChildren).Switch(),
|
root => root.AppState.SelectedTab.Value.SelectedsChildren.Value,
|
||||||
v => v.ItemsSource);
|
v => v.ItemsSource);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListView<IAppState, IItemViewModel> ParentsItemsView()
|
private ListView<IRootViewModel, IItemViewModel> ParentsItemsView()
|
||||||
{
|
{
|
||||||
var list = new ListView<IAppState, IItemViewModel>
|
var list = new ListView<IRootViewModel, IItemViewModel>
|
||||||
{
|
{
|
||||||
DataContext = _consoleAppState,
|
ListPadding = 8
|
||||||
ApplicationContext = _applicationContext,
|
|
||||||
ListPadding = 8,
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(0, 0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
list.ItemTemplate = item =>
|
list.ItemTemplate = item =>
|
||||||
@@ -249,67 +303,12 @@ public class MainWindow
|
|||||||
|
|
||||||
list.Bind(
|
list.Bind(
|
||||||
list,
|
list,
|
||||||
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.ParentsChildren).Switch(),
|
root => root.AppState.SelectedTab.Value.ParentsChildren.Value,
|
||||||
v => v.ItemsSource);
|
v => v.ItemsSource);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TestGrid()
|
|
||||||
{
|
|
||||||
var grid = new Grid<object>
|
|
||||||
{
|
|
||||||
ApplicationContext = _applicationContext,
|
|
||||||
ColumnDefinitionsObject = "Auto Auto",
|
|
||||||
RowDefinitionsObject = "Auto Auto",
|
|
||||||
ChildInitializer =
|
|
||||||
{
|
|
||||||
new Rectangle<object>
|
|
||||||
{
|
|
||||||
Fill = new ConsoleColor(System.ConsoleColor.Blue, ColorType.Foreground),
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(0, 0)
|
|
||||||
},
|
|
||||||
Width = 2,
|
|
||||||
Height = 2,
|
|
||||||
},
|
|
||||||
new Rectangle<object>
|
|
||||||
{
|
|
||||||
Fill = new ConsoleColor(System.ConsoleColor.Red, ColorType.Foreground),
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(0, 1)
|
|
||||||
},
|
|
||||||
Width = 3,
|
|
||||||
Height = 3,
|
|
||||||
},
|
|
||||||
new Rectangle<object>
|
|
||||||
{
|
|
||||||
Fill = new ConsoleColor(System.ConsoleColor.Green, ColorType.Foreground),
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(1, 0)
|
|
||||||
},
|
|
||||||
Width = 4,
|
|
||||||
Height = 4,
|
|
||||||
},
|
|
||||||
new Rectangle<object>
|
|
||||||
{
|
|
||||||
Fill = new ConsoleColor(System.ConsoleColor.Yellow, ColorType.Foreground),
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(1, 1)
|
|
||||||
},
|
|
||||||
Width = 5,
|
|
||||||
Height = 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//_grid = grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IView> RootViews() => new IView[]
|
public IEnumerable<IView> RootViews() => new IView[]
|
||||||
{
|
{
|
||||||
_root
|
_root
|
||||||
|
|||||||
19
src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs
Normal file
19
src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
|
public class RootViewModel : IRootViewModel
|
||||||
|
{
|
||||||
|
public string UserName => Environment.UserName;
|
||||||
|
public string MachineName => Environment.MachineName;
|
||||||
|
public IPossibleCommandsViewModel PossibleCommands { get; }
|
||||||
|
public IConsoleAppState AppState { get; }
|
||||||
|
|
||||||
|
public RootViewModel(
|
||||||
|
IConsoleAppState appState,
|
||||||
|
IPossibleCommandsViewModel possibleCommands)
|
||||||
|
{
|
||||||
|
AppState = appState;
|
||||||
|
PossibleCommands = possibleCommands;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ public static class Startup
|
|||||||
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.Configure<ConsoleApplicationConfiguration>(configuration);
|
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CircularBuffer", "Library\C
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.ConsoleUI.Styles", "ConsoleApp\FileTime.ConsoleUI.Styles\FileTime.ConsoleUI.Styles.csproj", "{CCB6F86A-7E80-448E-B543-DF9DB337C42A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.ConsoleUI.Styles", "ConsoleApp\FileTime.ConsoleUI.Styles\FileTime.ConsoleUI.Styles.csproj", "{CCB6F86A-7E80-448E-B543-DF9DB337C42A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservableComputations.Extensions", "Library\ObservableComputations.Extensions\ObservableComputations.Extensions.csproj", "{6C3C3151-9341-4792-9B0B-A11C0658524E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -333,6 +335,10 @@ Global
|
|||||||
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{CCB6F86A-7E80-448E-B543-DF9DB337C42A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6C3C3151-9341-4792-9B0B-A11C0658524E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6C3C3151-9341-4792-9B0B-A11C0658524E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6C3C3151-9341-4792-9B0B-A11C0658524E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6C3C3151-9341-4792-9B0B-A11C0658524E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -390,6 +396,7 @@ Global
|
|||||||
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
{AF4FE804-12D9-46E2-A584-BFF6D4509766} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
{AF4FE804-12D9-46E2-A584-BFF6D4509766} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
{CCB6F86A-7E80-448E-B543-DF9DB337C42A} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
|
{CCB6F86A-7E80-448E-B543-DF9DB337C42A} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
|
||||||
|
{6C3C3151-9341-4792-9B0B-A11C0658524E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using Avalonia.Data.Converters;
|
|
||||||
using FileTime.App.Core.UserCommand;
|
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.Converters;
|
|
||||||
|
|
||||||
public class CommandToCommandNameConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if(value is not IUserCommand command) return value;
|
|
||||||
|
|
||||||
//TODO: implement
|
|
||||||
return command.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}" x:Key="NotEqualsConverter" />
|
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}" x:Key="NotEqualsConverter" />
|
||||||
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.GreaterThan}" x:Key="GreaterThanConverter" />
|
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.GreaterThan}" x:Key="GreaterThanConverter" />
|
||||||
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter" />
|
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter" />
|
||||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter" />
|
|
||||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter" />
|
<converters:ItemToImageConverter x:Key="ItemToImageConverter" />
|
||||||
<converters:StringReplaceConverter
|
<converters:StringReplaceConverter
|
||||||
NewValue="/"
|
NewValue="/"
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ public interface IMainWindowViewModel : IMainWindowViewModelBase
|
|||||||
IAdminElevationManager AdminElevationManager { get; }
|
IAdminElevationManager AdminElevationManager { get; }
|
||||||
IClipboardService ClipboardService { get; }
|
IClipboardService ClipboardService { get; }
|
||||||
ITimelineViewModel TimelineViewModel { get; }
|
ITimelineViewModel TimelineViewModel { get; }
|
||||||
|
IPossibleCommandsViewModel PossibleCommands { get; }
|
||||||
Task RunOrOpenItem(IItemViewModel itemViewModel);
|
Task RunOrOpenItem(IItemViewModel itemViewModel);
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,7 @@ namespace FileTime.GuiApp.App.ViewModels;
|
|||||||
[Inject(typeof(IClipboardService), PropertyAccessModifier = AccessModifier.Public)]
|
[Inject(typeof(IClipboardService), PropertyAccessModifier = AccessModifier.Public)]
|
||||||
[Inject(typeof(IModalService), PropertyName = "_modalService")]
|
[Inject(typeof(IModalService), PropertyName = "_modalService")]
|
||||||
[Inject(typeof(ITimelineViewModel), PropertyAccessModifier = AccessModifier.Public)]
|
[Inject(typeof(ITimelineViewModel), PropertyAccessModifier = AccessModifier.Public)]
|
||||||
|
[Inject(typeof(IPossibleCommandsViewModel), PropertyName = "PossibleCommands",PropertyAccessModifier = AccessModifier.Public)]
|
||||||
public partial class MainWindowViewModel : IMainWindowViewModel
|
public partial class MainWindowViewModel : IMainWindowViewModel
|
||||||
{
|
{
|
||||||
public bool Loading => false;
|
public bool Loading => false;
|
||||||
|
|||||||
@@ -750,7 +750,7 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid IsVisible="{Binding AppState.PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
<Grid IsVisible="{Binding PossibleCommands.PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="1" />
|
<RowDefinition Height="1" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -763,17 +763,17 @@
|
|||||||
Margin="10,0"
|
Margin="10,0"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
|
|
||||||
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.PossibleCommands}">
|
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding PossibleCommands.PossibleCommands}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate x:DataType="configuration:CommandBindingConfiguration">
|
<DataTemplate x:DataType="corevm:IPossibleCommandEntryViewModel">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="200" />
|
<ColumnDefinition Width="200" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<TextBlock Text="{Binding KeysDisplayText}" />
|
<TextBlock Text="{Binding KeysText}" />
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Command, Converter={StaticResource CommandToCommandNameConverter}}" />
|
<TextBlock Grid.Column="1" Text="{Binding Title}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsRepeater.ItemTemplate>
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
|||||||
13
src/Library/ObservableComputations.Extensions/Extensions.cs
Normal file
13
src/Library/ObservableComputations.Extensions/Extensions.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace ObservableComputations;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
[ObservableComputationsCall]
|
||||||
|
public static Selecting<TSourceItem, TResultItem> Selecting<TSourceItem, TResultItem>(
|
||||||
|
this ReadOnlyObservableCollection<TSourceItem> source,
|
||||||
|
Expression<Func<TSourceItem, TResultItem>> selectorExpression)
|
||||||
|
=> new(source, selectorExpression);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>ObservableComputations</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ObservableComputations" Version="2.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -9,6 +9,7 @@ public class ApplicationContext : IApplicationContext
|
|||||||
public ILoggerFactory? LoggerFactory { get; init; }
|
public ILoggerFactory? LoggerFactory { get; init; }
|
||||||
public IEventLoop EventLoop { get; init; }
|
public IEventLoop EventLoop { get; init; }
|
||||||
public bool IsRunning { get; set; }
|
public bool IsRunning { get; set; }
|
||||||
|
public char EmptyCharacter { get; init; } = ' ';
|
||||||
|
|
||||||
public ApplicationContext()
|
public ApplicationContext()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class Binding<TDataContext, TExpressionResult, TResult> : IDisposable
|
|||||||
|
|
||||||
public Binding(
|
public Binding(
|
||||||
IView<TDataContext> dataSourceView,
|
IView<TDataContext> dataSourceView,
|
||||||
Expression<Func<TDataContext?, TExpressionResult>> dataContextExpression,
|
Expression<Func<TDataContext?, TExpressionResult>> dataSourceExpression,
|
||||||
object? propertySource,
|
object? propertySource,
|
||||||
PropertyInfo targetProperty,
|
PropertyInfo targetProperty,
|
||||||
Func<TExpressionResult, TResult> converter,
|
Func<TExpressionResult, TResult> converter,
|
||||||
@@ -28,18 +28,18 @@ public class Binding<TDataContext, TExpressionResult, TResult> : IDisposable
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(dataSourceView);
|
ArgumentNullException.ThrowIfNull(dataSourceView);
|
||||||
ArgumentNullException.ThrowIfNull(dataContextExpression);
|
ArgumentNullException.ThrowIfNull(dataSourceExpression);
|
||||||
ArgumentNullException.ThrowIfNull(targetProperty);
|
ArgumentNullException.ThrowIfNull(targetProperty);
|
||||||
ArgumentNullException.ThrowIfNull(converter);
|
ArgumentNullException.ThrowIfNull(converter);
|
||||||
|
|
||||||
_dataSourceView = dataSourceView;
|
_dataSourceView = dataSourceView;
|
||||||
_dataContextMapper = dataContextExpression.Compile();
|
_dataContextMapper = dataSourceExpression.Compile();
|
||||||
_propertySource = propertySource;
|
_propertySource = propertySource;
|
||||||
_targetProperty = targetProperty;
|
_targetProperty = targetProperty;
|
||||||
_converter = converter;
|
_converter = converter;
|
||||||
_fallbackValue = fallbackValue;
|
_fallbackValue = fallbackValue;
|
||||||
|
|
||||||
InitTrackingTree(dataContextExpression);
|
InitTrackingTree(dataSourceExpression);
|
||||||
|
|
||||||
UpdateTrackers();
|
UpdateTrackers();
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ public class Binding<TDataContext, TExpressionResult, TResult> : IDisposable
|
|||||||
{
|
{
|
||||||
if (propertySource is IDisposableCollection propertySourceDisposableCollection)
|
if (propertySource is IDisposableCollection propertySourceDisposableCollection)
|
||||||
{
|
{
|
||||||
propertySourceDisposableCollection.AddDisposable(this);
|
|
||||||
_propertySourceDisposableCollection = propertySourceDisposableCollection;
|
_propertySourceDisposableCollection = propertySourceDisposableCollection;
|
||||||
|
propertySourceDisposableCollection.AddDisposable(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void AttachChildren()
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.Attached = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override TChild AddChild<TChild>(TChild child)
|
public override TChild AddChild<TChild>(TChild child)
|
||||||
{
|
{
|
||||||
child = base.AddChild(child);
|
child = base.AddChild(child);
|
||||||
|
|||||||
@@ -1,16 +1,65 @@
|
|||||||
using TerminalUI.Models;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using TerminalUI.Models;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public abstract class ContentView<T>: View<T>, IContentRenderer
|
public abstract partial class ContentView<T> : View<T>, IContentRenderer<T>
|
||||||
{
|
{
|
||||||
|
private bool _placeholderRenderDone;
|
||||||
|
[Notify] private RenderMethod _contentRendererMethod;
|
||||||
|
private IView<T>? _content;
|
||||||
|
|
||||||
|
public IView<T>? Content
|
||||||
|
{
|
||||||
|
get => _content;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Equals(value, _content)) return;
|
||||||
|
|
||||||
|
if (_content is not null)
|
||||||
|
{
|
||||||
|
RemoveChild(_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = value;
|
||||||
|
|
||||||
|
if (_content is not null)
|
||||||
|
{
|
||||||
|
AddChild(_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected ContentView()
|
protected ContentView()
|
||||||
{
|
{
|
||||||
ContentRendererMethod = DefaultContentRender;
|
_contentRendererMethod = DefaultContentRender;
|
||||||
|
RerenderProperties.Add(nameof(Content));
|
||||||
|
RerenderProperties.Add(nameof(ContentRendererMethod));
|
||||||
}
|
}
|
||||||
public IView? Content { get; set; }
|
|
||||||
public Action<Position, Size> ContentRendererMethod { get; set; }
|
|
||||||
|
|
||||||
private void DefaultContentRender(Position position, Size size) => Content?.Render(position, size);
|
protected override void AttachChildren()
|
||||||
|
{
|
||||||
|
base.AttachChildren();
|
||||||
|
if (Content is not null)
|
||||||
|
{
|
||||||
|
Content.Attached = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DefaultContentRender(RenderContext renderContext, Position position, Size size)
|
||||||
|
{
|
||||||
|
if (Content is null)
|
||||||
|
{
|
||||||
|
if (_placeholderRenderDone) return false;
|
||||||
|
_placeholderRenderDone = true;
|
||||||
|
RenderEmpty(renderContext, position, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_placeholderRenderDone = false;
|
||||||
|
return Content.Render(renderContext, position, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
@@ -8,15 +9,79 @@ namespace TerminalUI.Controls;
|
|||||||
|
|
||||||
public class Grid<T> : ChildContainerView<T>
|
public class Grid<T> : ChildContainerView<T>
|
||||||
{
|
{
|
||||||
|
private List<RowDefinition> _rowDefinitions = new() {RowDefinition.Star(1)};
|
||||||
|
private List<ColumnDefinition> _columnDefinitions = new() {ColumnDefinition.Star(1)};
|
||||||
private ILogger<Grid<T>>? Logger => ApplicationContext?.LoggerFactory?.CreateLogger<Grid<T>>();
|
private ILogger<Grid<T>>? Logger => ApplicationContext?.LoggerFactory?.CreateLogger<Grid<T>>();
|
||||||
|
|
||||||
private delegate void WithSizes(Span<int> widths, Span<int> heights);
|
private delegate void WithSizes(RenderContext renderContext, Span<int> widths, Span<int> heights);
|
||||||
|
|
||||||
private delegate TResult WithSizes<TResult>(Span<int> widths, Span<int> heights);
|
private delegate TResult WithSizes<TResult>(RenderContext renderContext, Span<int> widths, Span<int> heights);
|
||||||
|
|
||||||
private const int ToBeCalculated = -1;
|
private const int ToBeCalculated = -1;
|
||||||
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new() {RowDefinition.Star(1)};
|
|
||||||
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new() {ColumnDefinition.Star(1)};
|
public IReadOnlyList<RowDefinition> RowDefinitions
|
||||||
|
{
|
||||||
|
get => _rowDefinitions;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var nextValue = value;
|
||||||
|
if (value.Count == 0)
|
||||||
|
{
|
||||||
|
nextValue = new List<RowDefinition> {RowDefinition.Star(1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
var needUpdate = nextValue.Count != _rowDefinitions.Count;
|
||||||
|
if (!needUpdate)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < nextValue.Count; i++)
|
||||||
|
{
|
||||||
|
if (!nextValue[i].Equals(_rowDefinitions[i]))
|
||||||
|
{
|
||||||
|
needUpdate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needUpdate)
|
||||||
|
{
|
||||||
|
_rowDefinitions = nextValue.ToList();
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ColumnDefinition> ColumnDefinitions
|
||||||
|
{
|
||||||
|
get => _columnDefinitions;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var nextValue = value;
|
||||||
|
if (value.Count == 0)
|
||||||
|
{
|
||||||
|
nextValue = new List<ColumnDefinition> {ColumnDefinition.Star(1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
var needUpdate = nextValue.Count != _columnDefinitions.Count;
|
||||||
|
if (!needUpdate)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < nextValue.Count; i++)
|
||||||
|
{
|
||||||
|
if (!nextValue[i].Equals(_columnDefinitions[i]))
|
||||||
|
{
|
||||||
|
needUpdate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needUpdate)
|
||||||
|
{
|
||||||
|
_columnDefinitions = nextValue.ToList();
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public object? ColumnDefinitionsObject
|
public object? ColumnDefinitionsObject
|
||||||
{
|
{
|
||||||
@@ -25,11 +90,7 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
{
|
{
|
||||||
if (value is IEnumerable<ColumnDefinition> columnDefinitions)
|
if (value is IEnumerable<ColumnDefinition> columnDefinitions)
|
||||||
{
|
{
|
||||||
ColumnDefinitions.Clear();
|
ColumnDefinitions = columnDefinitions.ToList();
|
||||||
foreach (var columnDefinition in columnDefinitions)
|
|
||||||
{
|
|
||||||
ColumnDefinitions.Add(columnDefinition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (value is string s)
|
else if (value is string s)
|
||||||
{
|
{
|
||||||
@@ -49,11 +110,7 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
{
|
{
|
||||||
if (value is IEnumerable<RowDefinition> rowDefinitions)
|
if (value is IEnumerable<RowDefinition> rowDefinitions)
|
||||||
{
|
{
|
||||||
RowDefinitions.Clear();
|
RowDefinitions = rowDefinitions.ToList();
|
||||||
foreach (var rowDefinition in rowDefinitions)
|
|
||||||
{
|
|
||||||
RowDefinitions.Add(rowDefinition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (value is string s)
|
else if (value is string s)
|
||||||
{
|
{
|
||||||
@@ -66,85 +123,135 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Size GetRequestedSize()
|
protected override Size CalculateSize()
|
||||||
=> WithCalculatedSize((columnWidths, rowHeights) =>
|
=> WithCalculatedSize(
|
||||||
{
|
RenderContext.Empty,
|
||||||
var width = 0;
|
new Option<Size>(new Size(0, 0), false),
|
||||||
var height = 0;
|
(_, columnWidths, rowHeights) =>
|
||||||
|
|
||||||
for (var i = 0; i < columnWidths.Length; i++)
|
|
||||||
{
|
{
|
||||||
width += columnWidths[i];
|
var width = 0;
|
||||||
}
|
var height = 0;
|
||||||
|
|
||||||
for (var i = 0; i < rowHeights.Length; i++)
|
for (var i = 0; i < columnWidths.Length; i++)
|
||||||
|
{
|
||||||
|
width += columnWidths[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < rowHeights.Length; i++)
|
||||||
|
{
|
||||||
|
height += rowHeights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Size(width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
|
=> WithCalculatedSize(
|
||||||
|
renderContext,
|
||||||
|
new Option<Size>(size, true),
|
||||||
|
(context, columnWidths, rowHeights) =>
|
||||||
{
|
{
|
||||||
height += rowHeights[i];
|
foreach (var child in Children)
|
||||||
}
|
|
||||||
|
|
||||||
return new Size(width, height);
|
|
||||||
}, new Option<Size>(new Size(0, 0), false));
|
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
|
||||||
=> WithCalculatedSize((columnWidths, rowHeights) =>
|
|
||||||
{
|
|
||||||
foreach (var child in Children)
|
|
||||||
{
|
|
||||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
|
||||||
var x = positionExtension?.Column ?? 0;
|
|
||||||
var y = positionExtension?.Row ?? 0;
|
|
||||||
|
|
||||||
if (x > columnWidths.Length)
|
|
||||||
{
|
{
|
||||||
Logger?.LogWarning("Child {Child} is out of bounds, x: {X}, y: {Y}", child, x, y);
|
var (x, y) = GetViewColumnAndRow(child, columnWidths.Length, rowHeights.Length);
|
||||||
x = 0;
|
|
||||||
|
var width = columnWidths[x];
|
||||||
|
var height = rowHeights[y];
|
||||||
|
|
||||||
|
var left = position.X;
|
||||||
|
var top = position.Y;
|
||||||
|
|
||||||
|
for (var i = 0; i < x; i++)
|
||||||
|
{
|
||||||
|
left += columnWidths[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < y; i++)
|
||||||
|
{
|
||||||
|
top += rowHeights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
child.Render(context, new Position(left, top), new Size(width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y > rowHeights.Length)
|
return true;
|
||||||
{
|
|
||||||
Logger?.LogWarning("Child {Child} is out of bounds, x: {X}, y: {Y}", child, x, y);
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var width = columnWidths[x];
|
/*var viewsByPosition = GroupViewsByPosition(columnWidths, rowHeights);
|
||||||
var height = rowHeights[y];
|
CleanUnusedArea(viewsByPosition, columnWidths, rowHeights);*/
|
||||||
|
});
|
||||||
|
|
||||||
var left = 0;
|
/*private void CleanUnusedArea(Dictionary<(int, int),List<IView>> viewsByPosition, Span<int> columnWidths, Span<int> rowHeights)
|
||||||
var top = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < x; i++)
|
|
||||||
{
|
|
||||||
left += columnWidths[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < y; i++)
|
|
||||||
{
|
|
||||||
top += rowHeights[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
child.Render(new Position(position.X + left, position.Y + top), new Size(width, height));
|
|
||||||
}
|
|
||||||
}, new Option<Size>(size, true));
|
|
||||||
|
|
||||||
private void WithCalculatedSize(WithSizes actionWithSizes, Option<Size> size)
|
|
||||||
{
|
{
|
||||||
WithCalculatedSize(Helper, size);
|
for (var x = 0; x < columnWidths.Length; x++)
|
||||||
|
|
||||||
object? Helper(Span<int> widths, Span<int> heights)
|
|
||||||
{
|
{
|
||||||
actionWithSizes(widths, heights);
|
for (var y = 0; y < rowHeights.Length; y++)
|
||||||
|
{
|
||||||
|
if (!viewsByPosition.TryGetValue((x, y), out var list)) continue;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*private Dictionary<(int, int), List<IView>> GroupViewsByPosition(int columns, int rows)
|
||||||
|
{
|
||||||
|
Dictionary<ValueTuple<int, int>, List<IView>> viewsByPosition = new();
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
var (x, y) = GetViewColumnAndRow(child, columns, rows);
|
||||||
|
if (viewsByPosition.TryGetValue((x, y), out var list))
|
||||||
|
{
|
||||||
|
list.Add(child);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
viewsByPosition[(x, y)] = new List<IView> {child};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewsByPosition;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private ValueTuple<int, int> GetViewColumnAndRow(IView view, int columns, int rows)
|
||||||
|
{
|
||||||
|
var positionExtension = view.GetExtension<GridPositionExtension>();
|
||||||
|
var x = positionExtension?.Column ?? 0;
|
||||||
|
var y = positionExtension?.Row ?? 0;
|
||||||
|
|
||||||
|
if (x > columns)
|
||||||
|
{
|
||||||
|
Logger?.LogWarning("Child {Child} is out of bounds, x: {X}, y: {Y}", view, x, y);
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > rows)
|
||||||
|
{
|
||||||
|
Logger?.LogWarning("Child {Child} is out of bounds, x: {X}, y: {Y}", view, x, y);
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithCalculatedSize(RenderContext renderContext, Option<Size> size, WithSizes actionWithSizes)
|
||||||
|
{
|
||||||
|
WithCalculatedSize(renderContext, size, Helper);
|
||||||
|
|
||||||
|
object? Helper(RenderContext renderContext1, Span<int> widths, Span<int> heights)
|
||||||
|
{
|
||||||
|
actionWithSizes(renderContext1, widths, heights);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TResult WithCalculatedSize<TResult>(WithSizes<TResult> actionWithSizes, Option<Size> size)
|
private TResult WithCalculatedSize<TResult>(RenderContext renderContext, Option<Size> size, WithSizes<TResult> actionWithSizes)
|
||||||
{
|
{
|
||||||
//TODO: Optimize it, dont calculate all of these, only if there is Auto value(s)
|
//TODO: Optimize it, dont calculate all of these, only if there is Auto value(s)
|
||||||
var columns = ColumnDefinitions.Count;
|
var columns = ColumnDefinitions.Count;
|
||||||
var rows = RowDefinitions.Count;
|
var rows = RowDefinitions.Count;
|
||||||
|
|
||||||
if (columns < 1) columns = 1;
|
Debug.Assert(columns > 0, "Columns must contain at least one element");
|
||||||
if (rows < 1) rows = 1;
|
Debug.Assert(rows > 0, "Rows must contain at least one element");
|
||||||
|
|
||||||
Span<int> allWidth = stackalloc int[columns * rows];
|
Span<int> allWidth = stackalloc int[columns * rows];
|
||||||
Span<int> allHeight = stackalloc int[columns * rows];
|
Span<int> allHeight = stackalloc int[columns * rows];
|
||||||
@@ -152,9 +259,7 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
var childSize = child.GetRequestedSize();
|
var childSize = child.GetRequestedSize();
|
||||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
var (x, y) = GetViewColumnAndRow(child, columns, rows);
|
||||||
var x = positionExtension?.Column ?? 0;
|
|
||||||
var y = positionExtension?.Row ?? 0;
|
|
||||||
|
|
||||||
allWidth.SetToMatrix(childSize.Width, x, y, columns);
|
allWidth.SetToMatrix(childSize.Width, x, y, columns);
|
||||||
allHeight.SetToMatrix(childSize.Height, x, y, columns);
|
allHeight.SetToMatrix(childSize.Height, x, y, columns);
|
||||||
@@ -246,60 +351,64 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionWithSizes(columnWidths, rowHeights);
|
return actionWithSizes(renderContext, columnWidths, rowHeights);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRowDefinitions(string value)
|
public void SetRowDefinitions(string value)
|
||||||
{
|
{
|
||||||
var values = value.Split(' ');
|
var values = value.Split(' ');
|
||||||
RowDefinitions.Clear();
|
var rowDefinitions = new List<RowDefinition>();
|
||||||
|
|
||||||
foreach (var v in values)
|
foreach (var v in values)
|
||||||
{
|
{
|
||||||
if (v == "Auto")
|
if (v == "Auto")
|
||||||
{
|
{
|
||||||
RowDefinitions.Add(RowDefinition.Auto);
|
rowDefinitions.Add(RowDefinition.Auto);
|
||||||
}
|
}
|
||||||
else if (v.EndsWith("*"))
|
else if (v.EndsWith("*"))
|
||||||
{
|
{
|
||||||
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
||||||
RowDefinitions.Add(RowDefinition.Star(starValue));
|
rowDefinitions.Add(RowDefinition.Star(starValue));
|
||||||
}
|
}
|
||||||
else if (int.TryParse(v, out var pixelValue))
|
else if (int.TryParse(v, out var pixelValue))
|
||||||
{
|
{
|
||||||
RowDefinitions.Add(RowDefinition.Pixel(pixelValue));
|
rowDefinitions.Add(RowDefinition.Pixel(pixelValue));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Invalid row definition: " + v);
|
throw new ArgumentException("Invalid row definition: " + v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowDefinitions = rowDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetColumnDefinitions(string value)
|
public void SetColumnDefinitions(string value)
|
||||||
{
|
{
|
||||||
var values = value.Split(' ');
|
var values = value.Split(' ');
|
||||||
ColumnDefinitions.Clear();
|
var columnDefinitions = new List<ColumnDefinition>();
|
||||||
|
|
||||||
foreach (var v in values)
|
foreach (var v in values)
|
||||||
{
|
{
|
||||||
if (v == "Auto")
|
if (v == "Auto")
|
||||||
{
|
{
|
||||||
ColumnDefinitions.Add(ColumnDefinition.Auto);
|
columnDefinitions.Add(ColumnDefinition.Auto);
|
||||||
}
|
}
|
||||||
else if (v.EndsWith("*"))
|
else if (v.EndsWith("*"))
|
||||||
{
|
{
|
||||||
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
||||||
ColumnDefinitions.Add(ColumnDefinition.Star(starValue));
|
columnDefinitions.Add(ColumnDefinition.Star(starValue));
|
||||||
}
|
}
|
||||||
else if (int.TryParse(v, out var pixelValue))
|
else if (int.TryParse(v, out var pixelValue))
|
||||||
{
|
{
|
||||||
ColumnDefinitions.Add(ColumnDefinition.Pixel(pixelValue));
|
columnDefinitions.Add(ColumnDefinition.Pixel(pixelValue));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Invalid column definition: " + v);
|
throw new ArgumentException("Invalid column definition: " + v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnDefinitions = columnDefinitions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,23 +4,29 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
|
public delegate bool RenderMethod(RenderContext renderContext, Position position, Size size);
|
||||||
|
|
||||||
public interface IView : INotifyPropertyChanged, IDisposableCollection
|
public interface IView : INotifyPropertyChanged, IDisposableCollection
|
||||||
{
|
{
|
||||||
object? DataContext { get; set; }
|
object? DataContext { get; set; }
|
||||||
int? MinWidth { get; set; }
|
int? MinWidth { get; set; }
|
||||||
int? MaxWidth { get; set; }
|
int? MaxWidth { get; set; }
|
||||||
int? Width { get; set; }
|
int? Width { get; set; }
|
||||||
|
int ActualWidth { get; }
|
||||||
int? MinHeight { get; set; }
|
int? MinHeight { get; set; }
|
||||||
int? MaxHeight { get; set; }
|
int? MaxHeight { get; set; }
|
||||||
int? Height { get; set; }
|
int? Height { get; set; }
|
||||||
|
int ActualHeight { get; }
|
||||||
|
Margin Margin { get; set; }
|
||||||
bool Attached { get; set; }
|
bool Attached { get; set; }
|
||||||
Size GetRequestedSize();
|
string? Name { get; set; }
|
||||||
IApplicationContext? ApplicationContext { get; set; }
|
IApplicationContext? ApplicationContext { get; set; }
|
||||||
List<object> Extensions { get; }
|
List<object> Extensions { get; }
|
||||||
|
RenderMethod RenderMethod { get; set; }
|
||||||
Action<Position, Size> RenderMethod { get; set; }
|
|
||||||
event Action<IView> Disposed;
|
event Action<IView> Disposed;
|
||||||
void Render(Position position, Size size);
|
|
||||||
|
Size GetRequestedSize();
|
||||||
|
bool Render(RenderContext renderContext, Position position, Size size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IView<T> : IView
|
public interface IView<T> : IView
|
||||||
@@ -39,8 +45,10 @@ public interface IView<T> : IView
|
|||||||
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||||
where TChild : IView<TDataContext>, new();
|
where TChild : IView<TDataContext>, new();
|
||||||
|
|
||||||
public TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
||||||
|
|
||||||
public TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||||
where TChild : IView<TDataContext>;
|
where TChild : IView<TDataContext>;
|
||||||
|
|
||||||
|
void RemoveChild<TDataContext>(IView<TDataContext> child);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using DeclarativeProperty;
|
using DeclarativeProperty;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
@@ -79,18 +80,30 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
|
|
||||||
if (_itemsSource is IDeclarativeProperty<ObservableCollection<TItem>> observableDeclarativeProperty)
|
if (_itemsSource is IDeclarativeProperty<ObservableCollection<TItem>> observableDeclarativeProperty)
|
||||||
{
|
{
|
||||||
observableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
throw new NotSupportedException();
|
||||||
_getItems = () => observableDeclarativeProperty.Value;
|
|
||||||
}
|
}
|
||||||
else if (_itemsSource is IDeclarativeProperty<ReadOnlyObservableCollection<TItem>> readOnlyObservableDeclarativeProperty)
|
else if (_itemsSource is IDeclarativeProperty<ReadOnlyObservableCollection<TItem>> readOnlyObservableDeclarativeProperty)
|
||||||
{
|
{
|
||||||
readOnlyObservableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
throw new NotSupportedException();
|
||||||
_getItems = () => readOnlyObservableDeclarativeProperty.Value;
|
|
||||||
}
|
}
|
||||||
else if (_itemsSource is IDeclarativeProperty<IEnumerable<TItem>> enumerableDeclarativeProperty)
|
else if (_itemsSource is IDeclarativeProperty<IEnumerable<TItem>> enumerableDeclarativeProperty)
|
||||||
{
|
{
|
||||||
enumerableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
throw new NotSupportedException();
|
||||||
_getItems = () => enumerableDeclarativeProperty.Value;
|
}
|
||||||
|
|
||||||
|
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
|
||||||
|
{
|
||||||
|
((INotifyCollectionChanged) observableDeclarative).CollectionChanged +=
|
||||||
|
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
||||||
|
|
||||||
|
_getItems = () => observableDeclarative;
|
||||||
|
}
|
||||||
|
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
|
||||||
|
{
|
||||||
|
((INotifyCollectionChanged) readOnlyObservableDeclarative).CollectionChanged +=
|
||||||
|
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
||||||
|
|
||||||
|
_getItems = () => readOnlyObservableDeclarative;
|
||||||
}
|
}
|
||||||
else if (_itemsSource is ICollection<TItem> collection)
|
else if (_itemsSource is ICollection<TItem> collection)
|
||||||
_getItems = () => collection;
|
_getItems = () => collection;
|
||||||
@@ -111,7 +124,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
public Func<ListViewItem<TItem>, IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||||
|
|
||||||
public ListView()
|
public ListView()
|
||||||
{
|
{
|
||||||
@@ -120,7 +133,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
RerenderProperties.Add(nameof(Orientation));
|
RerenderProperties.Add(nameof(Orientation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Size GetRequestedSize()
|
protected override Size CalculateSize()
|
||||||
{
|
{
|
||||||
InstantiateItemViews();
|
InstantiateItemViews();
|
||||||
if (_listViewItems is null || _listViewItemLength == 0)
|
if (_listViewItems is null || _listViewItemLength == 0)
|
||||||
@@ -147,19 +160,16 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
=> Orientation == Orientation.Vertical
|
||||||
if (Orientation == Orientation.Vertical)
|
? RenderVertical(renderContext, position, size)
|
||||||
RenderVertical(position, size);
|
: RenderHorizontal(renderContext, position, size);
|
||||||
else
|
|
||||||
RenderHorizontal(position, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderHorizontal(Position position, Size size)
|
private bool RenderHorizontal(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
//Note: no support for same width elements
|
//Note: no support for same width elements
|
||||||
var listViewItems = InstantiateItemViews();
|
var listViewItems = InstantiateItemViews();
|
||||||
if (listViewItems.Length == 0) return;
|
if (listViewItems.Length == 0) return false;
|
||||||
|
|
||||||
Span<Size> requestedSizes = stackalloc Size[_listViewItemLength];
|
Span<Size> requestedSizes = stackalloc Size[_listViewItemLength];
|
||||||
|
|
||||||
@@ -215,20 +225,22 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
width = size.Width - deltaX;
|
width = size.Width - deltaX;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Render(position with {X = position.X + deltaX}, size with {Width = width});
|
item.Render(renderContext, position with {X = position.X + deltaX}, size with {Width = width});
|
||||||
deltaX = nextDeltaX;
|
deltaX = nextDeltaX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderVertical(Position position, Size size)
|
private bool RenderVertical(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
//Note: only same height is supported
|
//Note: only same height is supported
|
||||||
var requestedItemSize = _requestedItemSize;
|
var requestedItemSize = _requestedItemSize;
|
||||||
if (requestedItemSize.Height == 0 || requestedItemSize.Width == 0)
|
if (requestedItemSize.Height == 0 || requestedItemSize.Width == 0)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
var listViewItems = InstantiateItemViews();
|
var listViewItems = InstantiateItemViews();
|
||||||
if (listViewItems.Length == 0) return;
|
if (listViewItems.Length == 0) return false;
|
||||||
|
|
||||||
var itemsToRender = listViewItems.Length;
|
var itemsToRender = listViewItems.Length;
|
||||||
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
||||||
@@ -264,7 +276,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
for (var i = renderStartIndex; i < lastItemIndex; i++)
|
for (var i = renderStartIndex; i < lastItemIndex; i++)
|
||||||
{
|
{
|
||||||
var item = listViewItems[i];
|
var item = listViewItems[i];
|
||||||
item.Render(position with {Y = position.Y + deltaY}, requestedItemSize with {Width = size.Width});
|
item.Render(renderContext, position with {Y = position.Y + deltaY}, requestedItemSize with {Width = size.Width});
|
||||||
deltaY += requestedItemSize.Height;
|
deltaY += requestedItemSize.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +288,8 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||||
driver.Write(placeholder);
|
driver.Write(placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Span<ListViewItem<TItem>> InstantiateItemViews()
|
private Span<ListViewItem<TItem>> InstantiateItemViews()
|
||||||
@@ -300,11 +314,16 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
{
|
{
|
||||||
var dataContext = items[i];
|
var dataContext = items[i];
|
||||||
var child = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
|
var child = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
|
||||||
child.Content = ItemTemplate(child);
|
var newContent = ItemTemplate(child);
|
||||||
ItemTemplate(child);
|
child.Content = newContent;
|
||||||
newListViewItems[i] = child;
|
newListViewItems[i] = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_listViewItems is not null)
|
||||||
|
{
|
||||||
|
ListViewItemPool.Return(_listViewItems);
|
||||||
|
}
|
||||||
|
|
||||||
_listViewItems = newListViewItems;
|
_listViewItems = newListViewItems;
|
||||||
_listViewItemLength = items.Count;
|
_listViewItemLength = items.Count;
|
||||||
listViewItems = newListViewItems[..items.Count];
|
listViewItems = newListViewItems[..items.Count];
|
||||||
@@ -324,5 +343,5 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
return _listViewItems;
|
return _listViewItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IView? DefaultItemTemplate(ListViewItem<TItem> listViewItem) => null;
|
private static IView<TItem>? DefaultItemTemplate(ListViewItem<TItem> listViewItem) => null;
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@ namespace TerminalUI.Controls;
|
|||||||
|
|
||||||
public class ListViewItem<T> : ContentView<T>
|
public class ListViewItem<T> : ContentView<T>
|
||||||
{
|
{
|
||||||
public override Size GetRequestedSize()
|
protected override Size CalculateSize()
|
||||||
{
|
{
|
||||||
if (Content is null) return new Size(0, 0);
|
if (Content is null) return new Size(0, 0);
|
||||||
return Content.GetRequestedSize();
|
return Content.GetRequestedSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
if (ContentRendererMethod is null)
|
if (ContentRendererMethod is null)
|
||||||
{
|
{
|
||||||
@@ -22,6 +22,6 @@ public class ListViewItem<T> : ContentView<T>
|
|||||||
+ DataContext?.GetType().Name);
|
+ DataContext?.GetType().Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentRendererMethod(position, size);
|
return ContentRendererMethod(renderContext, position, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,38 @@ namespace TerminalUI.Controls;
|
|||||||
|
|
||||||
public partial class Rectangle<T> : View<T>
|
public partial class Rectangle<T> : View<T>
|
||||||
{
|
{
|
||||||
[Notify] private IColor? _fill;
|
private record RenderState(
|
||||||
public override Size GetRequestedSize() => new(Width ?? 0, Height ?? 0);
|
Position Position,
|
||||||
|
Size Size,
|
||||||
|
IColor? Fill);
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
private RenderState? _lastRenderState;
|
||||||
|
|
||||||
|
[Notify] private IColor? _fill;
|
||||||
|
protected override Size CalculateSize() => new(Width ?? 0, Height ?? 0);
|
||||||
|
|
||||||
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
var s = new string('█', Width ?? size.Width);
|
var renderState = new RenderState(position, size, Fill);
|
||||||
ApplicationContext?.ConsoleDriver.SetBackgroundColor(Fill ?? new Color.ConsoleColor(System.ConsoleColor.Yellow, ColorType.Background));
|
if (!NeedsRerender(renderState) || Fill is null) return false;
|
||||||
ApplicationContext?.ConsoleDriver.SetForegroundColor(Fill ?? new Color.ConsoleColor(System.ConsoleColor.Yellow, ColorType.Foreground));
|
_lastRenderState = renderState;
|
||||||
var height = Height ?? size.Height;
|
|
||||||
|
var driver = renderContext.ConsoleDriver;
|
||||||
|
|
||||||
|
var s = new string('█', size.Width);
|
||||||
|
driver.SetBackgroundColor(Fill);
|
||||||
|
driver.SetForegroundColor(Fill);
|
||||||
|
|
||||||
|
var height = size.Height;
|
||||||
for (var i = 0; i < height; i++)
|
for (var i = 0; i < height; i++)
|
||||||
{
|
{
|
||||||
ApplicationContext?.ConsoleDriver.SetCursorPosition(position with {Y = position.Y + i});
|
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||||
ApplicationContext?.ConsoleDriver.Write(s);
|
driver.Write(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ public partial class StackPanel<T> : ChildContainerView<T>
|
|||||||
private readonly Dictionary<IView, Size> _requestedSizes = new();
|
private readonly Dictionary<IView, Size> _requestedSizes = new();
|
||||||
[Notify] private Orientation _orientation = Orientation.Vertical;
|
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||||
|
|
||||||
public override Size GetRequestedSize()
|
protected override Size CalculateSize()
|
||||||
{
|
{
|
||||||
_requestedSizes.Clear();
|
_requestedSizes.Clear();
|
||||||
var width = 0;
|
var width = 0;
|
||||||
@@ -35,20 +35,38 @@ public partial class StackPanel<T> : ChildContainerView<T>
|
|||||||
return new Size(width, height);
|
return new Size(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
var delta = 0;
|
var delta = 0;
|
||||||
|
var neededRerender = false;
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
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};
|
||||||
child.Render(childPosition, childSize);
|
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
neededRerender = child.Render(renderContext, childPosition, childSize) || neededRerender;
|
||||||
|
|
||||||
delta += Orientation == Orientation.Vertical
|
delta += Orientation == Orientation.Vertical
|
||||||
? childSize.Height
|
? childSize.Height
|
||||||
: childSize.Width;
|
: childSize.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return neededRerender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using PropertyChanged.SourceGenerator;
|
using System.ComponentModel;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
|
using TerminalUI.ConsoleDrivers;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
|
|
||||||
@@ -7,9 +9,16 @@ namespace TerminalUI.Controls;
|
|||||||
|
|
||||||
public partial class TextBlock<T> : View<T>
|
public partial class TextBlock<T> : View<T>
|
||||||
{
|
{
|
||||||
private record RenderContext(Position Position, string? Text, IColor? Foreground, IColor? Background);
|
private record RenderState(
|
||||||
|
Position Position,
|
||||||
|
Size Size,
|
||||||
|
string? Text,
|
||||||
|
IColor? Foreground,
|
||||||
|
IColor? Background);
|
||||||
|
|
||||||
private RenderContext? _renderContext;
|
private RenderState? _lastRenderState;
|
||||||
|
private string[]? _textLines;
|
||||||
|
private bool _placeholderRenderDone;
|
||||||
|
|
||||||
[Notify] private string? _text = string.Empty;
|
[Notify] private string? _text = string.Empty;
|
||||||
[Notify] private IColor? _foreground;
|
[Notify] private IColor? _foreground;
|
||||||
@@ -28,23 +37,41 @@ public partial class TextBlock<T> : View<T>
|
|||||||
RerenderProperties.Add(nameof(Foreground));
|
RerenderProperties.Add(nameof(Foreground));
|
||||||
RerenderProperties.Add(nameof(Background));
|
RerenderProperties.Add(nameof(Background));
|
||||||
RerenderProperties.Add(nameof(TextAlignment));
|
RerenderProperties.Add(nameof(TextAlignment));
|
||||||
|
|
||||||
|
((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(Text))
|
||||||
|
{
|
||||||
|
_textLines = Text?.Split(Environment.NewLine);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Size GetRequestedSize() => new(Text?.Length ?? 0, 1);
|
protected override Size CalculateSize() => new(_textLines?.Max(l => l.Length) ?? 0, _textLines?.Length ?? 0);
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
if (size.Width == 0 || size.Height == 0) return;
|
if (size.Width == 0 || size.Height == 0) return false;
|
||||||
|
|
||||||
var driver = ApplicationContext!.ConsoleDriver;
|
var driver = renderContext.ConsoleDriver;
|
||||||
var renderContext = new RenderContext(position, Text, _foreground, _background);
|
var renderState = new RenderState(position, size, Text, _foreground, _background);
|
||||||
if (!NeedsRerender(renderContext)) return;
|
if (!NeedsRerender(renderState)) return false;
|
||||||
|
|
||||||
_renderContext = renderContext;
|
_lastRenderState = renderState;
|
||||||
|
|
||||||
if (Text is null) return;
|
if (_textLines is null)
|
||||||
|
{
|
||||||
|
if (_placeholderRenderDone)
|
||||||
|
{
|
||||||
|
_placeholderRenderDone = true;
|
||||||
|
RenderEmpty(renderContext, position, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_placeholderRenderDone = false;
|
||||||
|
|
||||||
driver.SetCursorPosition(position);
|
|
||||||
driver.ResetColor();
|
driver.ResetColor();
|
||||||
if (Foreground is { } foreground)
|
if (Foreground is { } foreground)
|
||||||
{
|
{
|
||||||
@@ -56,19 +83,31 @@ public partial class TextBlock<T> : View<T>
|
|||||||
driver.SetBackgroundColor(background);
|
driver.SetBackgroundColor(background);
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = TextAlignment switch
|
RenderText(_textLines, driver, position, size);
|
||||||
{
|
|
||||||
TextAlignment.Right => string.Format($"{{0,{size.Width}}}", Text),
|
|
||||||
_ => string.Format($"{{0,{-size.Width}}}", Text)
|
|
||||||
};
|
|
||||||
if (text.Length > size.Width)
|
|
||||||
{
|
|
||||||
text = text[..size.Width];
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.Write(text);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NeedsRerender(RenderContext renderContext)
|
private void RenderText(string[] textLines, IConsoleDriver driver, Position position, Size size)
|
||||||
=> _renderContext is null || _renderContext != renderContext;
|
{
|
||||||
|
for (var i = 0; i < textLines.Length; i++)
|
||||||
|
{
|
||||||
|
var text = textLines[i];
|
||||||
|
text = TextAlignment switch
|
||||||
|
{
|
||||||
|
TextAlignment.Right => string.Format($"{{0,{size.Width}}}", text),
|
||||||
|
_ => string.Format($"{{0,{-size.Width}}}", text)
|
||||||
|
};
|
||||||
|
if (text.Length > size.Width)
|
||||||
|
{
|
||||||
|
text = text[..size.Width];
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||||
|
driver.Write(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,13 @@ public abstract partial class View<T> : IView<T>
|
|||||||
[Notify] private int? _minWidth;
|
[Notify] private int? _minWidth;
|
||||||
[Notify] private int? _maxWidth;
|
[Notify] private int? _maxWidth;
|
||||||
[Notify] private int? _width;
|
[Notify] private int? _width;
|
||||||
|
[Notify] private int _actualWidth;
|
||||||
[Notify] private int? _minHeight;
|
[Notify] private int? _minHeight;
|
||||||
[Notify] private int? _maxHeight;
|
[Notify] private int? _maxHeight;
|
||||||
[Notify] private int? _height;
|
[Notify] private int? _height;
|
||||||
|
[Notify] private int _actualHeight;
|
||||||
|
[Notify] private Margin _margin = new Margin(0, 0, 0, 0);
|
||||||
|
[Notify] private string? _name;
|
||||||
[Notify] private IApplicationContext? _applicationContext;
|
[Notify] private IApplicationContext? _applicationContext;
|
||||||
private bool _attached;
|
private bool _attached;
|
||||||
|
|
||||||
@@ -32,17 +36,50 @@ public abstract partial class View<T> : IView<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<object> Extensions { get; } = new();
|
public List<object> Extensions { get; } = new();
|
||||||
public Action<Position, Size> RenderMethod { get; set; }
|
public RenderMethod RenderMethod { get; set; }
|
||||||
public event Action<IView>? Disposed;
|
public event Action<IView>? Disposed;
|
||||||
protected List<string> RerenderProperties { get; } = new();
|
protected List<string> RerenderProperties { get; } = new();
|
||||||
|
|
||||||
protected View()
|
protected View()
|
||||||
{
|
{
|
||||||
RenderMethod = DefaultRenderer;
|
RenderMethod = DefaultRenderer;
|
||||||
|
|
||||||
|
RerenderProperties.Add(nameof(MinWidth));
|
||||||
|
RerenderProperties.Add(nameof(MaxWidth));
|
||||||
|
RerenderProperties.Add(nameof(MinHeight));
|
||||||
|
RerenderProperties.Add(nameof(MaxHeight));
|
||||||
|
RerenderProperties.Add(nameof(Margin));
|
||||||
|
|
||||||
((INotifyPropertyChanged) this).PropertyChanged += Handle_PropertyChanged;
|
((INotifyPropertyChanged) this).PropertyChanged += Handle_PropertyChanged;
|
||||||
}
|
}
|
||||||
public abstract Size GetRequestedSize();
|
|
||||||
|
public virtual Size GetRequestedSize()
|
||||||
|
{
|
||||||
|
var size = CalculateSize();
|
||||||
|
|
||||||
|
if (MinWidth.HasValue && size.Width < MinWidth.Value)
|
||||||
|
size = size with {Width = MinWidth.Value};
|
||||||
|
else if (MaxWidth.HasValue && size.Width > MaxWidth.Value)
|
||||||
|
size = size with {Width = MaxWidth.Value};
|
||||||
|
|
||||||
|
if (MinHeight.HasValue && size.Height < MinHeight.Value)
|
||||||
|
size = size with {Height = MinHeight.Value};
|
||||||
|
else if (MaxHeight.HasValue && size.Height > MaxHeight.Value)
|
||||||
|
size = size with {Height = MaxHeight.Value};
|
||||||
|
|
||||||
|
if (Margin.Left != 0 || Margin.Right != 0)
|
||||||
|
size = size with {Width = size.Width + Margin.Left + Margin.Right};
|
||||||
|
|
||||||
|
if (Margin.Top != 0 || Margin.Bottom != 0)
|
||||||
|
size = size with {Height = size.Height + Margin.Top + Margin.Bottom};
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Size CalculateSize();
|
||||||
|
|
||||||
|
|
||||||
protected virtual void AttachChildren()
|
protected virtual void AttachChildren()
|
||||||
{
|
{
|
||||||
@@ -50,7 +87,8 @@ public abstract partial class View<T> : IView<T>
|
|||||||
|
|
||||||
private void Handle_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
private void Handle_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName is not null
|
if (Attached
|
||||||
|
&& e.PropertyName is not null
|
||||||
&& (e.PropertyName == nameof(IView.DataContext)
|
&& (e.PropertyName == nameof(IView.DataContext)
|
||||||
|| RerenderProperties.Contains(e.PropertyName)
|
|| RerenderProperties.Contains(e.PropertyName)
|
||||||
)
|
)
|
||||||
@@ -60,10 +98,16 @@ public abstract partial class View<T> : IView<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void DefaultRenderer(Position position, Size size);
|
protected abstract bool DefaultRenderer(RenderContext renderContext, Position position, Size size);
|
||||||
|
|
||||||
public void Render(Position position, Size size)
|
public bool Render(RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
|
if (!Attached)
|
||||||
|
throw new InvalidOperationException("Cannot render unattached view");
|
||||||
|
|
||||||
|
ActualWidth = size.Width;
|
||||||
|
ActualHeight = size.Height;
|
||||||
|
|
||||||
if (RenderMethod is null)
|
if (RenderMethod is null)
|
||||||
{
|
{
|
||||||
throw new NullReferenceException(
|
throw new NullReferenceException(
|
||||||
@@ -74,7 +118,31 @@ public abstract partial class View<T> : IView<T>
|
|||||||
+ DataContext?.GetType().Name);
|
+ DataContext?.GetType().Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderMethod(position, size);
|
if (Margin.Left != 0 || Margin.Top != 0 || Margin.Right != 0 || Margin.Bottom != 0)
|
||||||
|
{
|
||||||
|
position = new Position(
|
||||||
|
X: position.X + Margin.Left,
|
||||||
|
Y: position.Y + Margin.Top
|
||||||
|
);
|
||||||
|
|
||||||
|
size = new Size(
|
||||||
|
size.Width - Margin.Left - Margin.Right,
|
||||||
|
size.Height - Margin.Top - Margin.Bottom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RenderMethod(renderContext, position, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RenderEmpty(RenderContext renderContext, Position position, Size size)
|
||||||
|
{
|
||||||
|
var driver = renderContext.ConsoleDriver;
|
||||||
|
var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width);
|
||||||
|
for (var i = 0; i < size.Height; i++)
|
||||||
|
{
|
||||||
|
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||||
|
driver.Write(placeHolder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
|
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
|
||||||
@@ -93,9 +161,9 @@ public abstract partial class View<T> : IView<T>
|
|||||||
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
||||||
{
|
{
|
||||||
child.DataContext = DataContext;
|
child.DataContext = DataContext;
|
||||||
child.ApplicationContext = ApplicationContext;
|
CopyCommonPropertiesToNewChild(child);
|
||||||
|
|
||||||
var mapper = new DataContextMapper<T>(this, d => child.DataContext = d);
|
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
||||||
AddDisposable(mapper);
|
AddDisposable(mapper);
|
||||||
child.AddDisposable(mapper);
|
child.AddDisposable(mapper);
|
||||||
|
|
||||||
@@ -106,15 +174,36 @@ public abstract partial class View<T> : IView<T>
|
|||||||
where TChild : IView<TDataContext>
|
where TChild : IView<TDataContext>
|
||||||
{
|
{
|
||||||
child.DataContext = dataContextMapper(DataContext);
|
child.DataContext = dataContextMapper(DataContext);
|
||||||
child.ApplicationContext = ApplicationContext;
|
CopyCommonPropertiesToNewChild(child);
|
||||||
|
|
||||||
var mapper = new DataContextMapper<T>(this, d => child.DataContext = dataContextMapper(d));
|
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
||||||
AddDisposable(mapper);
|
AddDisposable(mapper);
|
||||||
child.AddDisposable(mapper);
|
child.AddDisposable(mapper);
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CopyCommonPropertiesToNewChild(IView child)
|
||||||
|
{
|
||||||
|
child.ApplicationContext = ApplicationContext;
|
||||||
|
child.Attached = Attached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RemoveChild<TDataContext>(IView<TDataContext> child)
|
||||||
|
{
|
||||||
|
var mappers = _disposables
|
||||||
|
.Where(d => d is DataContextMapper<T, TDataContext> mapper && mapper.Target == child)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var mapper in mappers)
|
||||||
|
{
|
||||||
|
mapper.Dispose();
|
||||||
|
RemoveDisposable(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
child.Attached = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddDisposable(IDisposable disposable) => _disposables.Add(disposable);
|
public void AddDisposable(IDisposable disposable) => _disposables.Add(disposable);
|
||||||
public void RemoveDisposable(IDisposable disposable) => _disposables.Remove(disposable);
|
public void RemoveDisposable(IDisposable disposable) => _disposables.Remove(disposable);
|
||||||
|
|
||||||
|
|||||||
@@ -3,25 +3,32 @@ using TerminalUI.Controls;
|
|||||||
|
|
||||||
namespace TerminalUI;
|
namespace TerminalUI;
|
||||||
|
|
||||||
public class DataContextMapper<T> : IDisposable
|
public class DataContextMapper<TSource, TTarget> : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IView<T> _source;
|
private readonly Func<TSource?, TTarget?> _mapper;
|
||||||
private readonly Action<T?> _setter;
|
public IView<TSource> Source { get; }
|
||||||
|
public IView<TTarget> Target { get; }
|
||||||
|
|
||||||
public DataContextMapper(IView<T> source, Action<T?> setter)
|
public DataContextMapper(IView<TSource> source, IView<TTarget> target, Func<TSource?, TTarget?> mapper)
|
||||||
{
|
{
|
||||||
|
_mapper = mapper;
|
||||||
ArgumentNullException.ThrowIfNull(source);
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
|
||||||
_source = source;
|
Source = source;
|
||||||
_setter = setter;
|
Target = target;
|
||||||
source.PropertyChanged += SourceOnPropertyChanged;
|
source.PropertyChanged += SourceOnPropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SourceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
private void SourceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName != nameof(IView<object>.DataContext)) return;
|
if (e.PropertyName != nameof(IView<object>.DataContext)) return;
|
||||||
_setter(_source.DataContext);
|
Target.DataContext = _mapper(Source.DataContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => _source.PropertyChanged -= SourceOnPropertyChanged;
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Source.PropertyChanged -= SourceOnPropertyChanged;
|
||||||
|
Source.RemoveDisposable(this);
|
||||||
|
Target.RemoveDisposable(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -45,11 +45,12 @@ public class EventLoop : IEventLoop
|
|||||||
}
|
}
|
||||||
|
|
||||||
var size = _applicationContext.ConsoleDriver.GetWindowSize();
|
var size = _applicationContext.ConsoleDriver.GetWindowSize();
|
||||||
|
var renderContext = new RenderContext(_applicationContext.ConsoleDriver);
|
||||||
foreach (var view in viewsToRender)
|
foreach (var view in viewsToRender)
|
||||||
{
|
{
|
||||||
view.Attached = true;
|
view.Attached = true;
|
||||||
view.GetRequestedSize();
|
view.GetRequestedSize();
|
||||||
view.Render(new Position(0, 0), size);
|
view.Render(renderContext, new Position(0, 0), size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public static class Binding
|
|||||||
public static Binding<TDataContext, TResult, TResult> Bind<TView, TDataContext, TResult>(
|
public static Binding<TDataContext, TResult, TResult> Bind<TView, TDataContext, TResult>(
|
||||||
this TView targetView,
|
this TView targetView,
|
||||||
IView<TDataContext> dataSourceView,
|
IView<TDataContext> dataSourceView,
|
||||||
Expression<Func<TDataContext?, TResult>> dataContextExpression,
|
Expression<Func<TDataContext?, TResult>> dataSourceExpression,
|
||||||
Expression<Func<TView, TResult>> propertyExpression,
|
Expression<Func<TView, TResult>> propertyExpression,
|
||||||
TResult? fallbackValue = default)
|
TResult? fallbackValue = default)
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ public static class Binding
|
|||||||
|
|
||||||
return new Binding<TDataContext, TResult, TResult>(
|
return new Binding<TDataContext, TResult, TResult>(
|
||||||
dataSourceView,
|
dataSourceView,
|
||||||
dataContextExpression,
|
dataSourceExpression,
|
||||||
targetView,
|
targetView,
|
||||||
propertyInfo,
|
propertyInfo,
|
||||||
value => value,
|
value => value,
|
||||||
@@ -29,7 +29,7 @@ public static class Binding
|
|||||||
public static Binding<TDataContext, TExpressionResult, TResult> Bind<TView, TDataContext, TExpressionResult, TResult>(
|
public static Binding<TDataContext, TExpressionResult, TResult> Bind<TView, TDataContext, TExpressionResult, TResult>(
|
||||||
this TView targetView,
|
this TView targetView,
|
||||||
IView<TDataContext> dataSourceView,
|
IView<TDataContext> dataSourceView,
|
||||||
Expression<Func<TDataContext?, TExpressionResult>> dataContextExpression,
|
Expression<Func<TDataContext?, TExpressionResult>> dataSourceExpression,
|
||||||
Expression<Func<TView, TResult>> propertyExpression,
|
Expression<Func<TView, TResult>> propertyExpression,
|
||||||
Func<TExpressionResult, TResult> converter,
|
Func<TExpressionResult, TResult> converter,
|
||||||
TResult? fallbackValue = default)
|
TResult? fallbackValue = default)
|
||||||
@@ -39,7 +39,7 @@ public static class Binding
|
|||||||
|
|
||||||
return new Binding<TDataContext, TExpressionResult, TResult>(
|
return new Binding<TDataContext, TExpressionResult, TResult>(
|
||||||
dataSourceView,
|
dataSourceView,
|
||||||
dataContextExpression,
|
dataSourceExpression,
|
||||||
targetView,
|
targetView,
|
||||||
propertyInfo,
|
propertyInfo,
|
||||||
converter,
|
converter,
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ public static class ViewExtensions
|
|||||||
{
|
{
|
||||||
public static T? GetExtension<T>(this IView view)
|
public static T? GetExtension<T>(this IView view)
|
||||||
=> (T?) view.Extensions.FirstOrDefault(e => e is T);
|
=> (T?) view.Extensions.FirstOrDefault(e => e is T);
|
||||||
|
|
||||||
|
public static IView<TDataContext> WithExtension<TDataContext>(this IView<TDataContext> view, object extension)
|
||||||
|
{
|
||||||
|
view.Extensions.Add(extension);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
public static ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext> WithDataContextMapper<TSourceDataContext, TTargetDataContext>(
|
public static ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext> WithDataContextMapper<TSourceDataContext, TTargetDataContext>(
|
||||||
this IView<TTargetDataContext> view,
|
this IView<TTargetDataContext> view,
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ public interface IApplicationContext
|
|||||||
bool IsRunning { get; set; }
|
bool IsRunning { get; set; }
|
||||||
IConsoleDriver ConsoleDriver { get; init; }
|
IConsoleDriver ConsoleDriver { get; init; }
|
||||||
ILoggerFactory? LoggerFactory { get; init; }
|
ILoggerFactory? LoggerFactory { get; init; }
|
||||||
|
char EmptyCharacter { get; init; }
|
||||||
}
|
}
|
||||||
18
src/Library/TerminalUI/Models/Margin.cs
Normal file
18
src/Library/TerminalUI/Models/Margin.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
|
public record Margin(int Left, int Top, int Right, int Bottom)
|
||||||
|
{
|
||||||
|
public static implicit operator Margin(int value) => new(value, value, value, value);
|
||||||
|
public static implicit operator Margin((int Left, int Top, int Right, int Bottom) value) => new(value.Left, value.Top, value.Right, value.Bottom);
|
||||||
|
public static implicit operator Margin(string s)
|
||||||
|
{
|
||||||
|
var parts = s.Split(' ');
|
||||||
|
return parts.Length switch
|
||||||
|
{
|
||||||
|
1 => new Margin(int.Parse(parts[0])),
|
||||||
|
2 => new Margin(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[0]), int.Parse(parts[1])),
|
||||||
|
4 => new Margin(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3])),
|
||||||
|
_ => throw new ArgumentException("Invalid margin format", nameof(s))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Library/TerminalUI/Models/RenderContext.cs
Normal file
18
src/Library/TerminalUI/Models/RenderContext.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
|
||||||
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
|
public readonly ref struct RenderContext
|
||||||
|
{
|
||||||
|
private static int _renderId = 0;
|
||||||
|
public readonly int RenderId;
|
||||||
|
public readonly IConsoleDriver ConsoleDriver;
|
||||||
|
|
||||||
|
public RenderContext(IConsoleDriver consoleDriver)
|
||||||
|
{
|
||||||
|
ConsoleDriver = consoleDriver;
|
||||||
|
RenderId = _renderId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RenderContext Empty => new(null!);
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace TerminalUI.Models;
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
public record struct Size(int Width, int Height);
|
public readonly record struct Size(int Width, int Height);
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Models;
|
|
||||||
|
|
||||||
namespace TerminalUI.Traits;
|
namespace TerminalUI.Traits;
|
||||||
|
|
||||||
public interface IContentRenderer
|
public interface IContentRenderer<T>
|
||||||
{
|
{
|
||||||
IView? Content { get; set; }
|
IView<T>? Content { get; set; }
|
||||||
Action<Position, Size> ContentRendererMethod { get; set; }
|
RenderMethod ContentRendererMethod { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user