Controls, Startup&Driver improvements
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
namespace FileTime.App.Core.Configuration;
|
||||
|
||||
public record ApplicationConfiguration(bool AllowCloseLastTab);
|
||||
@@ -4,4 +4,5 @@ public static class SectionNames
|
||||
{
|
||||
public const string KeybindingSectionName = "KeyBindings";
|
||||
public const string ProgramsSectionName = "Programs";
|
||||
public const string ApplicationSectionName = "Application";
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using DeclarativeProperty;
|
||||
using FileTime.App.CommandPalette.Services;
|
||||
using FileTime.App.Core.Configuration;
|
||||
using FileTime.App.Core.Extensions;
|
||||
using FileTime.App.Core.Models.Enums;
|
||||
using FileTime.App.Core.UserCommand;
|
||||
@@ -14,6 +15,7 @@ using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Local;
|
||||
using InitableService;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FileTime.App.Core.Services.UserCommandHandler;
|
||||
|
||||
@@ -29,6 +31,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
private readonly IFrequencyNavigationService _frequencyNavigationService;
|
||||
private readonly ICommandPaletteService _commandPaletteService;
|
||||
private readonly ILogger<NavigationUserCommandHandlerService> _logger;
|
||||
private readonly ApplicationConfiguration _applicationConfiguration;
|
||||
private ITabViewModel? _selectedTab;
|
||||
private IDeclarativeProperty<IContainer?>? _currentLocation;
|
||||
private IDeclarativeProperty<IItemViewModel?>? _currentSelectedItem;
|
||||
@@ -44,7 +47,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
IUserCommunicationService userCommunicationService,
|
||||
IFrequencyNavigationService frequencyNavigationService,
|
||||
ICommandPaletteService commandPaletteService,
|
||||
ILogger<NavigationUserCommandHandlerService> logger) : base(appState)
|
||||
ILogger<NavigationUserCommandHandlerService> logger,
|
||||
ApplicationConfiguration applicationConfiguration) : base(appState)
|
||||
{
|
||||
_appState = appState;
|
||||
_serviceProvider = serviceProvider;
|
||||
@@ -55,6 +59,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
_frequencyNavigationService = frequencyNavigationService;
|
||||
_commandPaletteService = commandPaletteService;
|
||||
_logger = logger;
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
|
||||
SaveSelectedTab(t => _selectedTab = t);
|
||||
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
|
||||
@@ -387,7 +392,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
|
||||
private Task CloseTab()
|
||||
{
|
||||
if (_appState.Tabs.Count < 2 || _selectedTab == null) return Task.CompletedTask;
|
||||
if ((!_applicationConfiguration.AllowCloseLastTab && _appState.Tabs.Count < 2) || _selectedTab == null) return Task.CompletedTask;
|
||||
|
||||
var tabToRemove = _selectedTab;
|
||||
_appState.RemoveTab(tabToRemove!);
|
||||
|
||||
@@ -41,20 +41,16 @@ public static class Startup
|
||||
.AddSingleton<IExitHandler, ContainerRefreshHandler>();
|
||||
}
|
||||
|
||||
private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection
|
||||
private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection) =>
|
||||
serviceCollection
|
||||
.AddSingleton<IUserCommandHandler, NavigationUserCommandHandlerService>()
|
||||
.AddSingleton<IUserCommandHandler, ItemManipulationUserCommandHandlerService>()
|
||||
.AddSingleton<IUserCommandHandler, ToolUserCommandHandlerService>()
|
||||
.AddSingleton<IUserCommandHandler, CommandSchedulerUserCommandHandlerService>();
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection, IConfigurationRoot configuration)
|
||||
{
|
||||
return serviceCollection
|
||||
internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection, IConfigurationRoot configuration) =>
|
||||
serviceCollection
|
||||
.Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName))
|
||||
.Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName))
|
||||
.AddSingleton<IConfiguration>(configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace FileTime.ConsoleUI.App.Configuration;
|
||||
|
||||
public class ConsoleApplicationConfiguration
|
||||
{
|
||||
public string? ConsoleDriver { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace FileTime.ConsoleUI.App;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using FileTime.App.Core.Models;
|
||||
using System.Collections.Specialized;
|
||||
using FileTime.App.Core.Models;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.ConsoleUI.App.KeyInputHandling;
|
||||
using TerminalUI;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
@@ -16,6 +18,7 @@ public class App : IApplication
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly IApplicationContext _applicationContext;
|
||||
private readonly IConsoleDriver _consoleDriver;
|
||||
private readonly IAppState _appState;
|
||||
private readonly IKeyInputHandlerService _keyInputHandlerService;
|
||||
private readonly Thread _renderThread;
|
||||
|
||||
@@ -26,7 +29,8 @@ public class App : IApplication
|
||||
IAppKeyService<ConsoleKey> appKeyService,
|
||||
MainWindow mainWindow,
|
||||
IApplicationContext applicationContext,
|
||||
IConsoleDriver consoleDriver)
|
||||
IConsoleDriver consoleDriver,
|
||||
IAppState appState)
|
||||
{
|
||||
_lifecycleService = lifecycleService;
|
||||
_keyInputHandlerService = keyInputHandlerService;
|
||||
@@ -35,6 +39,7 @@ public class App : IApplication
|
||||
_mainWindow = mainWindow;
|
||||
_applicationContext = applicationContext;
|
||||
_consoleDriver = consoleDriver;
|
||||
_appState = appState;
|
||||
|
||||
_renderThread = new Thread(Render);
|
||||
}
|
||||
@@ -43,6 +48,12 @@ public class App : IApplication
|
||||
{
|
||||
Task.Run(async () => await _lifecycleService.InitStartupHandlersAsync()).Wait();
|
||||
|
||||
((INotifyCollectionChanged) _appState.Tabs).CollectionChanged += (_, _) =>
|
||||
{
|
||||
if(_appState.Tabs.Count == 0)
|
||||
_applicationContext.IsRunning = false;
|
||||
};
|
||||
|
||||
_mainWindow.Initialize();
|
||||
foreach (var rootView in _mainWindow.RootViews())
|
||||
{
|
||||
@@ -66,6 +77,7 @@ public class App : IApplication
|
||||
_keyInputHandlerService.HandleKeyInput(keyEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.App.Core.ViewModels.Timeline;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
|
||||
namespace FileTime.ConsoleUI.App;
|
||||
|
||||
public class ConsoleAppState : AppStateBase, IConsoleAppState
|
||||
public partial class ConsoleAppState : AppStateBase, IConsoleAppState
|
||||
{
|
||||
[Notify] private string? _errorText;
|
||||
}
|
||||
@@ -12,6 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace FileTime.ConsoleUI.App;
|
||||
|
||||
public class MainConsoleConfiguration
|
||||
{
|
||||
public static Dictionary<string, string?> Configuration { get; }
|
||||
static MainConsoleConfiguration()
|
||||
{
|
||||
Configuration = new();
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@ using DeclarativeProperty;
|
||||
using FileTime.App.Core.Models.Enums;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using TerminalUI;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Controls;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.ViewExtensions;
|
||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
||||
|
||||
namespace FileTime.ConsoleUI.App;
|
||||
|
||||
@@ -17,6 +20,8 @@ public class MainWindow
|
||||
private readonly ITheme _theme;
|
||||
private ListView<IAppState, IItemViewModel> _selectedItemsView;
|
||||
|
||||
private Grid<object> _grid;
|
||||
|
||||
public MainWindow(
|
||||
IConsoleAppState consoleAppState,
|
||||
IApplicationContext applicationContext,
|
||||
@@ -56,9 +61,69 @@ public class MainWindow
|
||||
_selectedItemsView,
|
||||
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.CurrentItems).Switch(),
|
||||
v => v.ItemsSource);
|
||||
|
||||
TestGrid();
|
||||
}
|
||||
|
||||
public IEnumerable<IView> RootViews() => new IView[] {_selectedItemsView};
|
||||
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[]
|
||||
{
|
||||
_grid, _selectedItemsView
|
||||
};
|
||||
|
||||
private IColor? ToForegroundColor(ItemViewMode viewMode)
|
||||
=> viewMode switch
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.App.Core.Configuration;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.App.Core.ViewModels;
|
||||
using FileTime.ConsoleUI.App.Configuration;
|
||||
using FileTime.ConsoleUI.App.KeyInputHandling;
|
||||
using FileTime.ConsoleUI.App.Services;
|
||||
using FileTime.Core.Interactions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using TerminalUI;
|
||||
@@ -12,7 +15,7 @@ namespace FileTime.ConsoleUI.App;
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static IServiceCollection AddConsoleServices(this IServiceCollection services)
|
||||
public static IServiceCollection AddConsoleServices(this IServiceCollection services, IConfigurationRoot configuration)
|
||||
{
|
||||
services.TryAddSingleton<IApplication, App>();
|
||||
services.TryAddSingleton<MainWindow>();
|
||||
@@ -23,6 +26,9 @@ public static class Startup
|
||||
services.TryAddSingleton<IAppKeyService<ConsoleKey>, ConsoleAppKeyService>();
|
||||
services.TryAddSingleton<ISystemClipboardService, ConsoleSystemClipboardService>();
|
||||
services.AddSingleton<CustomLoggerSink>();
|
||||
services.TryAddSingleton(new ApplicationConfiguration(true));
|
||||
|
||||
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
||||
|
||||
services.TryAddSingleton<IApplicationContext>(sp
|
||||
=> new ApplicationContext
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FileTime.ConsoleUI.App;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
using ConsoleColor = TerminalUI.Models.ConsoleColor;
|
||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
||||
|
||||
namespace FileTime.ConsoleUI.Styles;
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ public static class DI
|
||||
{
|
||||
public static IServiceProvider ServiceProvider { get; private set; } = null!;
|
||||
|
||||
public static void Initialize(IConfigurationRoot configuration, IServiceCollection serviceCollection)
|
||||
public static IServiceProvider Initialize(IConfigurationRoot configuration)
|
||||
=> ServiceProvider = DependencyInjection
|
||||
.RegisterDefaultServices(configuration: configuration, serviceCollection: serviceCollection)
|
||||
.AddConsoleServices()
|
||||
.RegisterDefaultServices(configuration: configuration)
|
||||
.AddConsoleServices(configuration)
|
||||
.AddLocalProviderServices()
|
||||
.AddServerCoreServices()
|
||||
.AddFrequencyNavigation()
|
||||
@@ -31,6 +31,8 @@ public static class DI
|
||||
.AddCompression()
|
||||
.SetupLogging()
|
||||
.AddLogging(loggingBuilder => loggingBuilder.AddSerilog())
|
||||
.AddConsoleDriver()
|
||||
.AddTheme()
|
||||
.BuildServiceProvider();
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
|
||||
|
||||
26
src/ConsoleApp/FileTime.ConsoleUI/Help.cs
Normal file
26
src/ConsoleApp/FileTime.ConsoleUI/Help.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Text;
|
||||
using FileTime.App.Core.Configuration;
|
||||
using FileTime.ConsoleUI.App.Configuration;
|
||||
|
||||
namespace FileTime.ConsoleUI;
|
||||
|
||||
public static class Help
|
||||
{
|
||||
public static void PrintHelp()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
sb.AppendLine("Options:");
|
||||
PrintDriverOption(sb);
|
||||
Console.Write(sb.ToString());
|
||||
}
|
||||
|
||||
public static void PrintDriverOption(StringBuilder sb)
|
||||
{
|
||||
sb.AppendLine($"--{SectionNames.ApplicationSectionName}.{nameof(ConsoleApplicationConfiguration.ConsoleDriver)}");
|
||||
foreach (var driver in Startup.Drivers.Keys)
|
||||
{
|
||||
sb.AppendLine("\t" + driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,75 @@
|
||||
using FileTime.App.Core;
|
||||
using System.Diagnostics;
|
||||
using FileTime.App.Core;
|
||||
using FileTime.App.Core.Configuration;
|
||||
using FileTime.ConsoleUI;
|
||||
using FileTime.ConsoleUI.App;
|
||||
using FileTime.ConsoleUI.Styles;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Serilog;
|
||||
using Serilog.Debugging;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
|
||||
IConsoleDriver driver = new WindowsDriver();
|
||||
driver.Init();
|
||||
ITheme theme;
|
||||
if (driver.GetCursorPosition() is not {PosX: 0, PosY: 0})
|
||||
if(args.Contains("--help"))
|
||||
{
|
||||
driver = new DotnetDriver();
|
||||
driver.Init();
|
||||
theme = DefaultThemes.ConsoleColorTheme;
|
||||
}
|
||||
else
|
||||
{
|
||||
theme = DefaultThemes.Color256Theme;
|
||||
Help.PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
driver.SetCursorVisible(false);
|
||||
IConsoleDriver? driver = null;
|
||||
|
||||
(AppDataRoot, EnvironmentName) = Init.InitDevelopment();
|
||||
InitLogging();
|
||||
try
|
||||
{
|
||||
(AppDataRoot, EnvironmentName) = Init.InitDevelopment();
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(MainConfiguration.Configuration)
|
||||
#if DEBUG
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
#endif
|
||||
.Build();
|
||||
var configuration = CreateConfiguration(args);
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.TryAddSingleton<IConsoleDriver>(driver);
|
||||
serviceCollection.TryAddSingleton<ITheme>(theme);
|
||||
var serviceProvider = DI.Initialize(configuration);
|
||||
|
||||
DI.Initialize(configuration, serviceCollection);
|
||||
driver = serviceProvider.GetRequiredService<IConsoleDriver>();
|
||||
Log.Logger.Debug("Using driver {Driver}", driver.GetType().Name);
|
||||
driver.SetCursorVisible(false);
|
||||
|
||||
var app = DI.ServiceProvider.GetRequiredService<IApplication>();
|
||||
var app = serviceProvider.GetRequiredService<IApplication>();
|
||||
app.Run();
|
||||
}
|
||||
finally
|
||||
{
|
||||
driver.SetCursorVisible(true);
|
||||
driver.Dispose();
|
||||
driver?.SetCursorVisible(true);
|
||||
driver?.Dispose();
|
||||
}
|
||||
|
||||
static void InitLogging()
|
||||
{
|
||||
SelfLog.Enable(l => Debug.WriteLine(l));
|
||||
|
||||
var logFolder = Path.Combine(AppDataRoot, "logs", "bootstrap");
|
||||
|
||||
if (!Directory.Exists(logFolder)) Directory.CreateDirectory(logFolder);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
#if DEBUG || VERBOSE_LOGGING
|
||||
.MinimumLevel.Verbose()
|
||||
#endif
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.File(
|
||||
Path.Combine(logFolder, "appLog.log"),
|
||||
fileSizeLimitBytes: 10 * 1024 * 1024,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
rollOnFileSizeLimit: true)
|
||||
.CreateBootstrapLogger();
|
||||
}
|
||||
|
||||
static IConfigurationRoot CreateConfiguration(string[] strings)
|
||||
{
|
||||
var configurationRoot = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(MainConfiguration.Configuration)
|
||||
.AddInMemoryCollection(MainConsoleConfiguration.Configuration)
|
||||
#if DEBUG
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
#endif
|
||||
.AddCommandLine(strings)
|
||||
.Build();
|
||||
return configurationRoot;
|
||||
}
|
||||
|
||||
public partial class Program
|
||||
|
||||
66
src/ConsoleApp/FileTime.ConsoleUI/Startup.cs
Normal file
66
src/ConsoleApp/FileTime.ConsoleUI/Startup.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using FileTime.ConsoleUI.App;
|
||||
using FileTime.ConsoleUI.App.Configuration;
|
||||
using FileTime.ConsoleUI.Styles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
|
||||
namespace FileTime.ConsoleUI;
|
||||
|
||||
public static class Startup
|
||||
{
|
||||
public static readonly Dictionary<string, Func<IConsoleDriver>> Drivers = new()
|
||||
{
|
||||
["windows"] = () => new XTermDriver(),
|
||||
["dotnet"] = () => new DotnetDriver()
|
||||
};
|
||||
public static IServiceCollection AddConsoleDriver(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.TryAddSingleton<IConsoleDriver>(sp =>
|
||||
{
|
||||
var appConfig = sp.GetRequiredService<IOptions<ConsoleApplicationConfiguration>>();
|
||||
|
||||
IConsoleDriver? driver = null;
|
||||
if (appConfig.Value.ConsoleDriver is { } consoleDriver
|
||||
&& Drivers.TryGetValue(consoleDriver, out var driverFactory))
|
||||
{
|
||||
driver = driverFactory();
|
||||
driver.Init();
|
||||
}
|
||||
|
||||
if (driver == null)
|
||||
{
|
||||
driver = new XTermDriver();
|
||||
var asd = driver.GetCursorPosition();
|
||||
driver.Init();
|
||||
if (!driver.Init())
|
||||
{
|
||||
driver = new DotnetDriver();
|
||||
driver.Init();
|
||||
}
|
||||
}
|
||||
|
||||
return driver;
|
||||
});
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddTheme(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.TryAddSingleton<ITheme>(sp =>
|
||||
{
|
||||
var driver = sp.GetRequiredService<IConsoleDriver>();
|
||||
|
||||
return driver switch
|
||||
{
|
||||
XTermDriver _ => DefaultThemes.Color256Theme,
|
||||
DotnetDriver _ => DefaultThemes.ConsoleColorTheme,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(driver))
|
||||
};
|
||||
});
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ public static class Startup
|
||||
serviceCollection.TryAddSingleton<ToastMessageSink>();
|
||||
serviceCollection.TryAddSingleton<IUserCommunicationService>(s => s.GetRequiredService<IDialogService>());
|
||||
serviceCollection.TryAddSingleton<IAppKeyService<Key>, GuiAppKeyService>();
|
||||
serviceCollection.TryAddSingleton(new ApplicationConfiguration(false));
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public record struct Color256(byte Color, ColorType Type) : IColor
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public record struct ColorRgb(byte R, byte G, byte B, ColorType Type) : IColor
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public enum ColorType
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public static class Color256Colors
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public record ConsoleColor(System.ConsoleColor Color, ColorType Type) : IColor
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TerminalUI.Models;
|
||||
namespace TerminalUI.Color;
|
||||
|
||||
public interface IColor
|
||||
{
|
||||
@@ -1,13 +1,18 @@
|
||||
using TerminalUI.Models;
|
||||
using ConsoleColor = TerminalUI.Models.ConsoleColor;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
||||
|
||||
namespace TerminalUI.ConsoleDrivers;
|
||||
|
||||
public class DotnetDriver : IConsoleDriver
|
||||
{
|
||||
public virtual void Init() => Console.Clear();
|
||||
public virtual bool Init()
|
||||
{
|
||||
Console.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCursorPosition(Position position) => Console.SetCursorPosition(position.PosX, position.PosY);
|
||||
public void SetCursorPosition(Position position) => Console.SetCursorPosition(position.X, position.Y);
|
||||
|
||||
public void ResetColor() => Console.ResetColor();
|
||||
|
||||
@@ -21,8 +26,8 @@ public class DotnetDriver : IConsoleDriver
|
||||
|
||||
public void Write(char text) => Console.Write(text);
|
||||
|
||||
public virtual void Dispose() {}
|
||||
|
||||
public virtual void Dispose() => Console.Clear();
|
||||
|
||||
public bool CanRead() => Console.KeyAvailable;
|
||||
public ConsoleKeyInfo ReadKey() => Console.ReadKey(true);
|
||||
|
||||
@@ -36,6 +41,9 @@ public class DotnetDriver : IConsoleDriver
|
||||
public virtual void SetBackgroundColor(IColor background)
|
||||
{
|
||||
if (background is not ConsoleColor consoleColor) throw new NotSupportedException();
|
||||
Console.ForegroundColor = consoleColor.Color;
|
||||
Console.BackgroundColor = consoleColor.Color;
|
||||
}
|
||||
|
||||
public Size GetBufferSize() => new(Console.BufferWidth, Console.BufferHeight);
|
||||
public void Clear() => Console.Clear();
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.ConsoleDrivers;
|
||||
|
||||
public interface IConsoleDriver
|
||||
{
|
||||
void Init();
|
||||
bool Init();
|
||||
void Dispose();
|
||||
void SetCursorPosition(Position position);
|
||||
void ResetColor();
|
||||
@@ -16,4 +17,6 @@ public interface IConsoleDriver
|
||||
void SetCursorVisible(bool cursorVisible);
|
||||
void SetForegroundColor(IColor foreground);
|
||||
void SetBackgroundColor(IColor background);
|
||||
Size GetBufferSize();
|
||||
void Clear();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.ConsoleDrivers;
|
||||
|
||||
public sealed class WindowsDriver : DotnetDriver
|
||||
{
|
||||
public override void Init() => Console.Out.Write("\x1b[?1049h");
|
||||
|
||||
public override void Dispose() => Console.Out.Write("\x1b[?1049l");
|
||||
|
||||
public override void SetBackgroundColor(IColor background)
|
||||
=> Write(background.ToConsoleColor());
|
||||
|
||||
public override void SetForegroundColor(IColor foreground)
|
||||
=> Write(foreground.ToConsoleColor());
|
||||
}
|
||||
52
src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs
Normal file
52
src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
||||
|
||||
namespace TerminalUI.ConsoleDrivers;
|
||||
|
||||
public sealed class XTermDriver : DotnetDriver
|
||||
{
|
||||
private Position _initialCursorPosition;
|
||||
public override bool Init()
|
||||
{
|
||||
_initialCursorPosition = GetCursorPosition();
|
||||
Write("\x1b[?1047h");
|
||||
var isInitSuccessful = _initialCursorPosition == GetCursorPosition();
|
||||
if (isInitSuccessful)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
return isInitSuccessful;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Write("\x1b[?1047l");
|
||||
SetCursorPosition(_initialCursorPosition);
|
||||
}
|
||||
|
||||
public override void SetBackgroundColor(IColor background)
|
||||
{
|
||||
if (background is ConsoleColor consoleColor)
|
||||
{
|
||||
Console.BackgroundColor = consoleColor.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(background.ToConsoleColor());
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetForegroundColor(IColor foreground)
|
||||
{
|
||||
if (foreground is ConsoleColor consoleColor)
|
||||
{
|
||||
Console.ForegroundColor = consoleColor.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(foreground.ToConsoleColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public abstract class ContentView<T>: View<T>, IContentRenderer
|
||||
ContentRendererMethod = DefaultContentRender;
|
||||
}
|
||||
public IView? Content { get; set; }
|
||||
public Action<Position> ContentRendererMethod { get; set; }
|
||||
public Action<Position, Size> ContentRendererMethod { get; set; }
|
||||
|
||||
private void DefaultContentRender(Position position) => Content?.Render(position);
|
||||
private void DefaultContentRender(Position position, Size size) => Content?.Render(position, size);
|
||||
}
|
||||
248
src/Library/TerminalUI/Controls/Grid.cs
Normal file
248
src/Library/TerminalUI/Controls/Grid.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.ViewExtensions;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public class Grid<T> : View<T>
|
||||
{
|
||||
private const int ToBeCalculated = -1;
|
||||
private readonly ObservableCollection<IView> _children = new();
|
||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||
public GridChildInitializer<T> ChildInitializer { get; }
|
||||
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new();
|
||||
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new();
|
||||
|
||||
public object? ColumnDefinitionsObject
|
||||
{
|
||||
get => ColumnDefinitions;
|
||||
set
|
||||
{
|
||||
if (value is IEnumerable<ColumnDefinition> columnDefinitions)
|
||||
{
|
||||
ColumnDefinitions.Clear();
|
||||
foreach (var columnDefinition in columnDefinitions)
|
||||
{
|
||||
ColumnDefinitions.Add(columnDefinition);
|
||||
}
|
||||
}
|
||||
else if (value is string s)
|
||||
{
|
||||
SetColumnDefinitions(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object? RowDefinitionsObject
|
||||
{
|
||||
get => RowDefinitions;
|
||||
set
|
||||
{
|
||||
if (value is IEnumerable<RowDefinition> rowDefinitions)
|
||||
{
|
||||
RowDefinitions.Clear();
|
||||
foreach (var rowDefinition in rowDefinitions)
|
||||
{
|
||||
RowDefinitions.Add(rowDefinition);
|
||||
}
|
||||
}
|
||||
else if (value is string s)
|
||||
{
|
||||
SetRowDefinitions(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Grid()
|
||||
{
|
||||
ChildInitializer = new GridChildInitializer<T>(this);
|
||||
Children = new ReadOnlyObservableCollection<IView>(_children);
|
||||
_children.CollectionChanged += (o, e) =>
|
||||
{
|
||||
if (Attached)
|
||||
{
|
||||
if (e.NewItems?.OfType<IView>() is { } newItems)
|
||||
{
|
||||
foreach (var newItem in newItems)
|
||||
{
|
||||
newItem.Attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Size GetRequestedSize() => throw new NotImplementedException();
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
//TODO: Optimize it, dont calculate all of these only if there is Auto value(s)
|
||||
var columns = ColumnDefinitions.Count;
|
||||
Span<int> allWidth = stackalloc int[columns * RowDefinitions.Count];
|
||||
Span<int> allHeight = stackalloc int[columns * RowDefinitions.Count];
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var childSize = child.GetRequestedSize();
|
||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||
var x = positionExtension?.Column ?? 0;
|
||||
var y = positionExtension?.Row ?? 0;
|
||||
|
||||
allWidth.SetToMatrix(childSize.Width, x, y, columns);
|
||||
allHeight.SetToMatrix(childSize.Height, x, y, columns);
|
||||
}
|
||||
|
||||
Span<int> columnWidths = stackalloc int[columns];
|
||||
Span<int> rowHeights = stackalloc int[RowDefinitions.Count];
|
||||
|
||||
for (var i = 0; i < columnWidths.Length; i++)
|
||||
{
|
||||
if (ColumnDefinitions[i].Type == GridUnitType.Pixel)
|
||||
{
|
||||
columnWidths[i] = ColumnDefinitions[i].Value;
|
||||
}
|
||||
else if (ColumnDefinitions[i].Type == GridUnitType.Star)
|
||||
{
|
||||
columnWidths[i] = ToBeCalculated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var max = 0;
|
||||
for (var j = 0; j < RowDefinitions.Count; j++)
|
||||
{
|
||||
max = Math.Max(max, allWidth.GetFromMatrix(i, j, columns));
|
||||
}
|
||||
|
||||
columnWidths[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < rowHeights.Length; i++)
|
||||
{
|
||||
if (RowDefinitions[i].Type == GridUnitType.Pixel)
|
||||
{
|
||||
rowHeights[i] = RowDefinitions[i].Value;
|
||||
}
|
||||
else if (RowDefinitions[i].Type == GridUnitType.Star)
|
||||
{
|
||||
rowHeights[i] = ToBeCalculated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var max = 0;
|
||||
for (var j = 0; j < columns; j++)
|
||||
{
|
||||
max = Math.Max(max, allHeight.GetFromMatrix(j, i, columns));
|
||||
}
|
||||
|
||||
rowHeights[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var childSize = child.GetRequestedSize();
|
||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||
var x = positionExtension?.Column ?? 0;
|
||||
var y = positionExtension?.Row ?? 0;
|
||||
|
||||
var width = columnWidths[x];
|
||||
var height = rowHeights[y];
|
||||
|
||||
var left = 0;
|
||||
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(left, top), new Size(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRowDefinitions(string value)
|
||||
{
|
||||
var values = value.Split(' ');
|
||||
RowDefinitions.Clear();
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
if (v == "Auto")
|
||||
{
|
||||
RowDefinitions.Add(RowDefinition.Auto);
|
||||
}
|
||||
else if (v.EndsWith("*"))
|
||||
{
|
||||
var starValue = int.Parse(v[0..^1]);
|
||||
RowDefinitions.Add(RowDefinition.Star(starValue));
|
||||
}
|
||||
else if (int.TryParse(v, out var pixelValue))
|
||||
{
|
||||
RowDefinitions.Add(RowDefinition.Pixel(pixelValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid row definition: " + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetColumnDefinitions(string value)
|
||||
{
|
||||
var values = value.Split(' ');
|
||||
ColumnDefinitions.Clear();
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
if (v == "Auto")
|
||||
{
|
||||
ColumnDefinitions.Add(ColumnDefinition.Auto);
|
||||
}
|
||||
else if (v.EndsWith("*"))
|
||||
{
|
||||
var starValue = int.Parse(v[0..^1]);
|
||||
ColumnDefinitions.Add(ColumnDefinition.Star(starValue));
|
||||
}
|
||||
else if (int.TryParse(v, out var pixelValue))
|
||||
{
|
||||
ColumnDefinitions.Add(ColumnDefinition.Pixel(pixelValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid column definition: " + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild>(TChild child)
|
||||
{
|
||||
child = base.AddChild(child);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TDataContext : default
|
||||
{
|
||||
child = base.AddChild(child, dataContextMapper);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
24
src/Library/TerminalUI/Controls/GridChildInitializer.cs
Normal file
24
src/Library/TerminalUI/Controls/GridChildInitializer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public record ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext>(IView<TTargetDataContext> Child, Func<TSourceDataContext?, TTargetDataContext?> DataContextMapper);
|
||||
|
||||
public class GridChildInitializer<T> : IEnumerable<IView>
|
||||
{
|
||||
private readonly Grid<T> _grid;
|
||||
|
||||
public GridChildInitializer(Grid<T> grid)
|
||||
{
|
||||
_grid = grid;
|
||||
}
|
||||
|
||||
public void Add(IView<T> item) => _grid.AddChild(item);
|
||||
|
||||
public void Add<TDataContext>(ChildWithDataContextMapper<T, TDataContext> item)
|
||||
=> _grid.AddChild(item.Child, item.DataContextMapper);
|
||||
|
||||
public IEnumerator<IView> GetEnumerator() => _grid.Children.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
23
src/Library/TerminalUI/Controls/GridProperties.cs
Normal file
23
src/Library/TerminalUI/Controls/GridProperties.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
|
||||
public enum GridUnitType
|
||||
{
|
||||
Auto,
|
||||
Pixel,
|
||||
Star
|
||||
}
|
||||
|
||||
public record struct RowDefinition(GridUnitType Type, int Value)
|
||||
{
|
||||
public static RowDefinition Auto => new(GridUnitType.Auto, 0);
|
||||
public static RowDefinition Pixel(int value) => new(GridUnitType.Pixel, value);
|
||||
public static RowDefinition Star(int value) => new(GridUnitType.Star, value);
|
||||
}
|
||||
|
||||
public record struct ColumnDefinition(GridUnitType Type, int Value)
|
||||
{
|
||||
public static ColumnDefinition Auto => new(GridUnitType.Auto, 0);
|
||||
public static ColumnDefinition Pixel(int value) => new(GridUnitType.Pixel, value);
|
||||
public static ColumnDefinition Star(int value) => new(GridUnitType.Star, value);
|
||||
}
|
||||
@@ -7,10 +7,20 @@ namespace TerminalUI.Controls;
|
||||
public interface IView : INotifyPropertyChanged, IDisposableCollection
|
||||
{
|
||||
object? DataContext { get; set; }
|
||||
Action<Position> RenderMethod { get; set; }
|
||||
IApplicationContext? ApplicationContext { get; init;}
|
||||
int? MinWidth { get; set; }
|
||||
int? MaxWidth { get; set; }
|
||||
int? Width { get; set; }
|
||||
int? MinHeight { get; set; }
|
||||
int? MaxHeight { get; set; }
|
||||
int? Height { get; set; }
|
||||
bool Attached { get; set; }
|
||||
Size GetRequestedSize();
|
||||
IApplicationContext? ApplicationContext { get; set; }
|
||||
List<object> Extensions { get; }
|
||||
|
||||
Action<Position, Size> RenderMethod { get; set; }
|
||||
event Action<IView> Disposed;
|
||||
void Render(Position position);
|
||||
void Render(Position position, Size size);
|
||||
}
|
||||
|
||||
public interface IView<T> : IView
|
||||
@@ -28,4 +38,9 @@ public interface IView<T> : IView
|
||||
|
||||
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>, new();
|
||||
|
||||
TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
||||
|
||||
TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using DeclarativeProperty;
|
||||
using TerminalUI.Models;
|
||||
|
||||
@@ -14,6 +15,23 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
private object? _itemsSource;
|
||||
private ListViewItem<TItem>[]? _listViewItems;
|
||||
private int _listViewItemLength;
|
||||
private int _selectedIndex = 0;
|
||||
private int _renderStartIndex = 0;
|
||||
private Size _requestedItemSize = new(0, 0);
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get => _selectedIndex;
|
||||
set
|
||||
{
|
||||
if (_selectedIndex != value)
|
||||
{
|
||||
_selectedIndex = value;
|
||||
OnPropertyChanged();
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object? ItemsSource
|
||||
{
|
||||
@@ -64,13 +82,53 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
|
||||
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||
|
||||
protected override void DefaultRenderer(Position position)
|
||||
public override Size GetRequestedSize()
|
||||
{
|
||||
if (_listViewItems is null || _listViewItems.Length == 0)
|
||||
return new Size(0, 0);
|
||||
|
||||
|
||||
var itemSize = _listViewItems[0].GetRequestedSize();
|
||||
_requestedItemSize = itemSize;
|
||||
return itemSize with {Height = itemSize.Height * _listViewItems.Length};
|
||||
}
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
var listViewItems = InstantiateItemViews();
|
||||
var deltaY = 0;
|
||||
foreach (var item in listViewItems)
|
||||
if (listViewItems.Length == 0) return;
|
||||
|
||||
var requestedItemSize = _requestedItemSize;
|
||||
|
||||
var itemsToRender = listViewItems.Length;
|
||||
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
||||
var renderStartIndex = _renderStartIndex;
|
||||
if (heightNeeded < size.Height)
|
||||
{
|
||||
item.Render(position with {PosY = position.PosY + deltaY++});
|
||||
var maxItemsToRender = (int) Math.Floor((double) size.Height / requestedItemSize.Height);
|
||||
if (SelectedIndex < renderStartIndex)
|
||||
{
|
||||
renderStartIndex = SelectedIndex - 1;
|
||||
}
|
||||
else if (SelectedIndex > renderStartIndex + maxItemsToRender)
|
||||
{
|
||||
renderStartIndex = SelectedIndex - maxItemsToRender + 1;
|
||||
}
|
||||
|
||||
if(renderStartIndex < 0)
|
||||
renderStartIndex = 0;
|
||||
else if (renderStartIndex + maxItemsToRender > listViewItems.Length)
|
||||
renderStartIndex = listViewItems.Length - maxItemsToRender;
|
||||
|
||||
_renderStartIndex = renderStartIndex;
|
||||
}
|
||||
|
||||
var deltaY = 0;
|
||||
for (var i = renderStartIndex; i < itemsToRender && i < listViewItems.Length; i++)
|
||||
{
|
||||
var item = listViewItems[i];
|
||||
item.Render(position with {Y = position.Y + deltaY}, requestedItemSize);
|
||||
deltaY += requestedItemSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,13 @@ namespace TerminalUI.Controls;
|
||||
|
||||
public class ListViewItem<T> : ContentView<T>
|
||||
{
|
||||
protected override void DefaultRenderer(Position position)
|
||||
public override Size GetRequestedSize()
|
||||
{
|
||||
if (Content is null) return new Size(0, 0);
|
||||
return Content.GetRequestedSize();
|
||||
}
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
if (ContentRendererMethod is null)
|
||||
{
|
||||
@@ -16,6 +22,6 @@ public class ListViewItem<T> : ContentView<T>
|
||||
+ DataContext?.GetType().Name);
|
||||
}
|
||||
|
||||
ContentRendererMethod(position);
|
||||
ContentRendererMethod(position, size);
|
||||
}
|
||||
}
|
||||
24
src/Library/TerminalUI/Controls/Rectangle.cs
Normal file
24
src/Library/TerminalUI/Controls/Rectangle.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public partial class Rectangle<T> : View<T>
|
||||
{
|
||||
[Notify] private IColor? _fill;
|
||||
public override Size GetRequestedSize() => new(Width ?? 0, Height ?? 0);
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
var s = new string('█', Width ?? size.Width);
|
||||
ApplicationContext?.ConsoleDriver.SetBackgroundColor(Fill ?? new Color.ConsoleColor(System.ConsoleColor.Yellow, ColorType.Background));
|
||||
ApplicationContext?.ConsoleDriver.SetForegroundColor(Fill ?? new Color.ConsoleColor(System.ConsoleColor.Yellow, ColorType.Foreground));
|
||||
var height = Height ?? size.Height;
|
||||
for (var i = 0; i < height; i++)
|
||||
{
|
||||
ApplicationContext?.ConsoleDriver.SetCursorPosition(position with {Y = position.Y + i});
|
||||
ApplicationContext?.ConsoleDriver.Write(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Models;
|
||||
|
||||
@@ -27,7 +28,9 @@ public partial class TextBlock<T> : View<T>
|
||||
RerenderProperties.Add(nameof(Background));
|
||||
}
|
||||
|
||||
protected override void DefaultRenderer(Position position)
|
||||
public override Size GetRequestedSize() => new(Text?.Length ?? 0, 1);
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
var driver = ApplicationContext!.ConsoleDriver;
|
||||
var renderContext = new RenderContext(position, Text, _foreground, _background);
|
||||
|
||||
@@ -10,8 +10,30 @@ public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
private readonly List<IDisposable> _disposables = new();
|
||||
[Notify] private T? _dataContext;
|
||||
public Action<Position> RenderMethod { get; set; }
|
||||
public IApplicationContext? ApplicationContext { get; init; }
|
||||
[Notify] private int? _minWidth;
|
||||
[Notify] private int? _maxWidth;
|
||||
[Notify] private int? _width;
|
||||
[Notify] private int? _minHeight;
|
||||
[Notify] private int? _maxHeight;
|
||||
[Notify] private int? _height;
|
||||
private bool _attached;
|
||||
|
||||
public bool Attached
|
||||
{
|
||||
get => _attached;
|
||||
set
|
||||
{
|
||||
if (_attached == value) return;
|
||||
_attached = value;
|
||||
if (value)
|
||||
{
|
||||
AttachChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
public List<object> Extensions { get; } = new();
|
||||
public Action<Position, Size> RenderMethod { get; set; }
|
||||
public IApplicationContext? ApplicationContext { get; set; }
|
||||
public event Action<IView>? Disposed;
|
||||
protected List<string> RerenderProperties { get; } = new();
|
||||
|
||||
@@ -20,22 +42,27 @@ public abstract partial class View<T> : IView<T>
|
||||
RenderMethod = DefaultRenderer;
|
||||
((INotifyPropertyChanged) this).PropertyChanged += Handle_PropertyChanged;
|
||||
}
|
||||
public abstract Size GetRequestedSize();
|
||||
|
||||
protected virtual void AttachChildren()
|
||||
{
|
||||
}
|
||||
|
||||
private void Handle_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is not null
|
||||
&& (e.PropertyName == nameof(IView.DataContext)
|
||||
if (e.PropertyName is not null
|
||||
&& (e.PropertyName == nameof(IView.DataContext)
|
||||
|| RerenderProperties.Contains(e.PropertyName)
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void DefaultRenderer(Position position);
|
||||
protected abstract void DefaultRenderer(Position position, Size size);
|
||||
|
||||
public void Render(Position position)
|
||||
public void Render(Position position, Size size)
|
||||
{
|
||||
if (RenderMethod is null)
|
||||
{
|
||||
@@ -47,16 +74,27 @@ public abstract partial class View<T> : IView<T>
|
||||
+ DataContext?.GetType().Name);
|
||||
}
|
||||
|
||||
RenderMethod(position);
|
||||
RenderMethod(position, size);
|
||||
}
|
||||
|
||||
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
|
||||
{
|
||||
var child = new TChild
|
||||
{
|
||||
DataContext = DataContext,
|
||||
ApplicationContext = ApplicationContext
|
||||
};
|
||||
var child = new TChild();
|
||||
return AddChild(child);
|
||||
}
|
||||
|
||||
public TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>, new()
|
||||
{
|
||||
var child = new TChild();
|
||||
return AddChild(child, dataContextMapper);
|
||||
}
|
||||
|
||||
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
||||
{
|
||||
child.DataContext = DataContext;
|
||||
child.ApplicationContext = ApplicationContext;
|
||||
|
||||
var mapper = new DataContextMapper<T>(this, d => child.DataContext = d);
|
||||
AddDisposable(mapper);
|
||||
child.AddDisposable(mapper);
|
||||
@@ -64,14 +102,12 @@ public abstract partial class View<T> : IView<T>
|
||||
return child;
|
||||
}
|
||||
|
||||
public TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>, new()
|
||||
public virtual TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>
|
||||
{
|
||||
var child = new TChild
|
||||
{
|
||||
DataContext = dataContextMapper(DataContext),
|
||||
ApplicationContext = ApplicationContext
|
||||
};
|
||||
child.DataContext = dataContextMapper(DataContext);
|
||||
child.ApplicationContext = ApplicationContext;
|
||||
|
||||
var mapper = new DataContextMapper<T>(this, d => child.DataContext = dataContextMapper(d));
|
||||
AddDisposable(mapper);
|
||||
child.AddDisposable(mapper);
|
||||
|
||||
@@ -45,9 +45,11 @@ public class EventLoop : IEventLoop
|
||||
viewsToRender = _viewsToRender.ToList();
|
||||
}
|
||||
|
||||
var size =_applicationContext.ConsoleDriver.GetBufferSize();
|
||||
foreach (var view in viewsToRender)
|
||||
{
|
||||
view.Render(new Position(0, 0));
|
||||
view.Attached = true;
|
||||
view.Render(new Position(0, 0), size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
src/Library/TerminalUI/Extensions/SpanExtensions.cs
Normal file
7
src/Library/TerminalUI/Extensions/SpanExtensions.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace TerminalUI.Extensions;
|
||||
|
||||
public static class SpanExtensions
|
||||
{
|
||||
public static T GetFromMatrix<T>(this Span<T> span, int x, int y, int width) => span[y * width + x];
|
||||
public static void SetToMatrix<T>(this Span<T> span, T value, int x, int y, int width) => span[y * width + x] = value;
|
||||
}
|
||||
14
src/Library/TerminalUI/Extensions/ViewExtensions.cs
Normal file
14
src/Library/TerminalUI/Extensions/ViewExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using TerminalUI.Controls;
|
||||
|
||||
namespace TerminalUI.Extensions;
|
||||
|
||||
public static class ViewExtensions
|
||||
{
|
||||
public static T? GetExtension<T>(this IView view)
|
||||
=> (T?) view.Extensions.FirstOrDefault(e => e is T);
|
||||
|
||||
public static ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext> WithDataContextMapper<TSourceDataContext, TTargetDataContext>(
|
||||
this IView<TTargetDataContext> view,
|
||||
Func<TSourceDataContext?, TTargetDataContext?> dataContextMapper)
|
||||
=> new(view, dataContextMapper);
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace TerminalUI.Models;
|
||||
|
||||
public record struct Position(int PosX, int PosY);
|
||||
public record struct Position(int X, int Y);
|
||||
3
src/Library/TerminalUI/Models/Size.cs
Normal file
3
src/Library/TerminalUI/Models/Size.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace TerminalUI.Models;
|
||||
|
||||
public record Size(int Width, int Height);
|
||||
12
src/Library/TerminalUI/Rendering/RenderingEngine.cs
Normal file
12
src/Library/TerminalUI/Rendering/RenderingEngine.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using TerminalUI.Controls;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Rendering;
|
||||
|
||||
public class RenderingEngine
|
||||
{
|
||||
public static void Asd(IView root, Size bufferSize)
|
||||
{
|
||||
var rootSize = root.GetRequestedSize();
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,5 @@ namespace TerminalUI.Traits;
|
||||
public interface IContentRenderer
|
||||
{
|
||||
IView? Content { get; set; }
|
||||
Action<Position> ContentRendererMethod { get; set; }
|
||||
Action<Position, Size> ContentRendererMethod { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace TerminalUI.ViewExtensions;
|
||||
|
||||
public record GridPositionExtension(int Row, int Column);
|
||||
@@ -56,11 +56,11 @@ void HandleStartup(Action action)
|
||||
IConfigurationRoot CreateConfiguration()
|
||||
{
|
||||
var configurationBuilder = new ConfigurationBuilder();
|
||||
configurationBuilder.AddCommandLine(args);
|
||||
#if DEBUG
|
||||
configurationBuilder.AddJsonFile("appsettings.Development.json", optional: true);
|
||||
configurationBuilder.AddJsonFile("appsettings.Local.json", optional: true);
|
||||
#endif
|
||||
configurationBuilder.AddCommandLine(args);
|
||||
return configurationBuilder.Build();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ public class CompressionUserCommandHandler : AggregatedUserCommandHandler
|
||||
IClipboardService clipboardService)
|
||||
{
|
||||
_clipboardService = clipboardService;
|
||||
_markedItems = appState.SelectedTab.Map(t => t.MarkedItems).Switch();
|
||||
_selectedItem = appState.SelectedTab.Map(t => t.CurrentSelectedItem).Switch();
|
||||
_markedItems = appState.SelectedTab.Map(t => t?.MarkedItems).Switch();
|
||||
_selectedItem = appState.SelectedTab.Map(t => t?.CurrentSelectedItem).Switch();
|
||||
|
||||
AddCommandHandler(new[]
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user