diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/ApplicationConfiguration.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/ApplicationConfiguration.cs new file mode 100644 index 0000000..618d100 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/ApplicationConfiguration.cs @@ -0,0 +1,3 @@ +namespace FileTime.App.Core.Configuration; + +public record ApplicationConfiguration(bool AllowCloseLastTab); \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/SectionNames.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/SectionNames.cs index 23254a0..74bfb73 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/SectionNames.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Configuration/SectionNames.cs @@ -4,4 +4,5 @@ public static class SectionNames { public const string KeybindingSectionName = "KeyBindings"; public const string ProgramsSectionName = "Programs"; + public const string ApplicationSectionName = "Application"; } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs index 6626074..03cad9e 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs @@ -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 _logger; + private readonly ApplicationConfiguration _applicationConfiguration; private ITabViewModel? _selectedTab; private IDeclarativeProperty? _currentLocation; private IDeclarativeProperty? _currentSelectedItem; @@ -44,7 +47,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase IUserCommunicationService userCommunicationService, IFrequencyNavigationService frequencyNavigationService, ICommandPaletteService commandPaletteService, - ILogger logger) : base(appState) + ILogger 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!); diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index 3efaf2f..6485880 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -41,20 +41,16 @@ public static class Startup .AddSingleton(); } - private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection) - { - return serviceCollection + private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection) => + serviceCollection .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); - } - internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection, IConfigurationRoot configuration) - { - return serviceCollection + internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection, IConfigurationRoot configuration) => + serviceCollection .Configure(configuration.GetSection(SectionNames.ProgramsSectionName)) .Configure(configuration.GetSection(SectionNames.KeybindingSectionName)) .AddSingleton(configuration); - } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs new file mode 100644 index 0000000..c6c22e0 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Configuration/ConsoleApplicationConfiguration.cs @@ -0,0 +1,6 @@ +namespace FileTime.ConsoleUI.App.Configuration; + +public class ConsoleApplicationConfiguration +{ + public string? ConsoleDriver { get; set; } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/ITheme.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/ITheme.cs index ade5fed..cdf8f48 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/ITheme.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/ITheme.cs @@ -1,4 +1,5 @@ -using TerminalUI.Models; +using TerminalUI.Color; +using TerminalUI.Models; namespace FileTime.ConsoleUI.App; diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs index 2f9e8d6..5c034c9 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs @@ -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 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); } } diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleAppState.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleAppState.cs index 476e029..6ff5d5c 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleAppState.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleAppState.cs @@ -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; } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj b/src/ConsoleApp/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj index 93c5f85..11cca50 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj @@ -12,6 +12,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainConsoleConfiguration.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainConsoleConfiguration.cs new file mode 100644 index 0000000..7aed740 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainConsoleConfiguration.cs @@ -0,0 +1,10 @@ +namespace FileTime.ConsoleUI.App; + +public class MainConsoleConfiguration +{ + public static Dictionary Configuration { get; } + static MainConsoleConfiguration() + { + Configuration = new(); + } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs index 704491f..339f664 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs @@ -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 _selectedItemsView; + private Grid _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 RootViews() => new IView[] {_selectedItemsView}; + private void TestGrid() + { + var grid = new Grid + { + ApplicationContext = _applicationContext, + ColumnDefinitionsObject = "Auto Auto", + RowDefinitionsObject = "Auto Auto", + ChildInitializer = + { + new Rectangle + { + Fill = new ConsoleColor(System.ConsoleColor.Blue, ColorType.Foreground), + Extensions = + { + new GridPositionExtension(0, 0) + }, + Width = 2, + Height = 2, + }, + new Rectangle + { + Fill = new ConsoleColor(System.ConsoleColor.Red, ColorType.Foreground), + Extensions = + { + new GridPositionExtension(0, 1) + }, + Width = 3, + Height = 3, + }, + new Rectangle + { + Fill = new ConsoleColor(System.ConsoleColor.Green, ColorType.Foreground), + Extensions = + { + new GridPositionExtension(1, 0) + }, + Width = 4, + Height = 4, + }, + new Rectangle + { + Fill = new ConsoleColor(System.ConsoleColor.Yellow, ColorType.Foreground), + Extensions = + { + new GridPositionExtension(1, 1) + }, + Width = 5, + Height = 5, + } + } + }; + + _grid = grid; + } + + public IEnumerable RootViews() => new IView[] + { + _grid, _selectedItemsView + }; private IColor? ToForegroundColor(ItemViewMode viewMode) => viewMode switch diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs index bc34e16..13d5573 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs @@ -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(); services.TryAddSingleton(); @@ -23,6 +26,9 @@ public static class Startup services.TryAddSingleton, ConsoleAppKeyService>(); services.TryAddSingleton(); services.AddSingleton(); + services.TryAddSingleton(new ApplicationConfiguration(true)); + + services.Configure(configuration); services.TryAddSingleton(sp => new ApplicationContext diff --git a/src/ConsoleApp/FileTime.ConsoleUI.Styles/DefaultTheme.cs b/src/ConsoleApp/FileTime.ConsoleUI.Styles/DefaultTheme.cs index 8055b86..4eecf21 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.Styles/DefaultTheme.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.Styles/DefaultTheme.cs @@ -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; diff --git a/src/ConsoleApp/FileTime.ConsoleUI/DI.cs b/src/ConsoleApp/FileTime.ConsoleUI/DI.cs index 8aa2fb9..3b30423 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/DI.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/DI.cs @@ -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(); diff --git a/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj b/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj index 55412d3..0fbc906 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj +++ b/src/ConsoleApp/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj @@ -23,6 +23,7 @@ + diff --git a/src/ConsoleApp/FileTime.ConsoleUI/Help.cs b/src/ConsoleApp/FileTime.ConsoleUI/Help.cs new file mode 100644 index 0000000..db47f74 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI/Help.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs index 739b025..df3ced5 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/Program.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/Program.cs @@ -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(driver); - serviceCollection.TryAddSingleton(theme); + var serviceProvider = DI.Initialize(configuration); - DI.Initialize(configuration, serviceCollection); + driver = serviceProvider.GetRequiredService(); + Log.Logger.Debug("Using driver {Driver}", driver.GetType().Name); + driver.SetCursorVisible(false); - var app = DI.ServiceProvider.GetRequiredService(); + var app = serviceProvider.GetRequiredService(); 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 diff --git a/src/ConsoleApp/FileTime.ConsoleUI/Startup.cs b/src/ConsoleApp/FileTime.ConsoleUI/Startup.cs new file mode 100644 index 0000000..35d517f --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI/Startup.cs @@ -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> Drivers = new() + { + ["windows"] = () => new XTermDriver(), + ["dotnet"] = () => new DotnetDriver() + }; + public static IServiceCollection AddConsoleDriver(this IServiceCollection serviceCollection) + { + serviceCollection.TryAddSingleton(sp => + { + var appConfig = sp.GetRequiredService>(); + + 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(sp => + { + var driver = sp.GetRequiredService(); + + return driver switch + { + XTermDriver _ => DefaultThemes.Color256Theme, + DotnetDriver _ => DefaultThemes.ConsoleColorTheme, + _ => throw new ArgumentOutOfRangeException(nameof(driver)) + }; + }); + + return serviceCollection; + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs index c256a06..111f135 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Startup.cs @@ -60,6 +60,7 @@ public static class Startup serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(s => s.GetRequiredService()); serviceCollection.TryAddSingleton, GuiAppKeyService>(); + serviceCollection.TryAddSingleton(new ApplicationConfiguration(false)); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/src/Library/TerminalUI/Models/Color256.cs b/src/Library/TerminalUI/Color/Color256.cs similarity index 93% rename from src/Library/TerminalUI/Models/Color256.cs rename to src/Library/TerminalUI/Color/Color256.cs index c1a4dc9..d79f58d 100644 --- a/src/Library/TerminalUI/Models/Color256.cs +++ b/src/Library/TerminalUI/Color/Color256.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace TerminalUI.Models; +namespace TerminalUI.Color; public record struct Color256(byte Color, ColorType Type) : IColor { diff --git a/src/Library/TerminalUI/Models/ColorRGB.cs b/src/Library/TerminalUI/Color/ColorRGB.cs similarity index 93% rename from src/Library/TerminalUI/Models/ColorRGB.cs rename to src/Library/TerminalUI/Color/ColorRGB.cs index cb9c028..df83dfa 100644 --- a/src/Library/TerminalUI/Models/ColorRGB.cs +++ b/src/Library/TerminalUI/Color/ColorRGB.cs @@ -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 { diff --git a/src/Library/TerminalUI/Models/ColorType.cs b/src/Library/TerminalUI/Color/ColorType.cs similarity index 68% rename from src/Library/TerminalUI/Models/ColorType.cs rename to src/Library/TerminalUI/Color/ColorType.cs index 3223daf..b07a8b2 100644 --- a/src/Library/TerminalUI/Models/ColorType.cs +++ b/src/Library/TerminalUI/Color/ColorType.cs @@ -1,4 +1,4 @@ -namespace TerminalUI.Models; +namespace TerminalUI.Color; public enum ColorType { diff --git a/src/Library/TerminalUI/Models/Colors.cs b/src/Library/TerminalUI/Color/Colors.cs similarity index 99% rename from src/Library/TerminalUI/Models/Colors.cs rename to src/Library/TerminalUI/Color/Colors.cs index ec53eae..f74e082 100644 --- a/src/Library/TerminalUI/Models/Colors.cs +++ b/src/Library/TerminalUI/Color/Colors.cs @@ -1,4 +1,4 @@ -namespace TerminalUI.Models; +namespace TerminalUI.Color; public static class Color256Colors { diff --git a/src/Library/TerminalUI/Models/ConsoleColor.cs b/src/Library/TerminalUI/Color/ConsoleColor.cs similarity index 83% rename from src/Library/TerminalUI/Models/ConsoleColor.cs rename to src/Library/TerminalUI/Color/ConsoleColor.cs index 1ebcd58..3968689 100644 --- a/src/Library/TerminalUI/Models/ConsoleColor.cs +++ b/src/Library/TerminalUI/Color/ConsoleColor.cs @@ -1,4 +1,4 @@ -namespace TerminalUI.Models; +namespace TerminalUI.Color; public record ConsoleColor(System.ConsoleColor Color, ColorType Type) : IColor { diff --git a/src/Library/TerminalUI/Models/IColor.cs b/src/Library/TerminalUI/Color/IColor.cs similarity index 72% rename from src/Library/TerminalUI/Models/IColor.cs rename to src/Library/TerminalUI/Color/IColor.cs index cb0a683..e693ef2 100644 --- a/src/Library/TerminalUI/Models/IColor.cs +++ b/src/Library/TerminalUI/Color/IColor.cs @@ -1,4 +1,4 @@ -namespace TerminalUI.Models; +namespace TerminalUI.Color; public interface IColor { diff --git a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs index 8a28efa..0bcffe0 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs @@ -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(); } \ No newline at end of file diff --git a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs index 52b2632..01646fe 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs @@ -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(); } \ No newline at end of file diff --git a/src/Library/TerminalUI/ConsoleDrivers/WindowsDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/WindowsDriver.cs deleted file mode 100644 index 927e431..0000000 --- a/src/Library/TerminalUI/ConsoleDrivers/WindowsDriver.cs +++ /dev/null @@ -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()); -} \ No newline at end of file diff --git a/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs new file mode 100644 index 0000000..0848763 --- /dev/null +++ b/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/ContentView.cs b/src/Library/TerminalUI/Controls/ContentView.cs index 1e0955e..9cae970 100644 --- a/src/Library/TerminalUI/Controls/ContentView.cs +++ b/src/Library/TerminalUI/Controls/ContentView.cs @@ -10,7 +10,7 @@ public abstract class ContentView: View, IContentRenderer ContentRendererMethod = DefaultContentRender; } public IView? Content { get; set; } - public Action ContentRendererMethod { get; set; } + public Action ContentRendererMethod { get; set; } - private void DefaultContentRender(Position position) => Content?.Render(position); + private void DefaultContentRender(Position position, Size size) => Content?.Render(position, size); } \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/Grid.cs b/src/Library/TerminalUI/Controls/Grid.cs new file mode 100644 index 0000000..0dc3529 --- /dev/null +++ b/src/Library/TerminalUI/Controls/Grid.cs @@ -0,0 +1,248 @@ +using System.Collections.ObjectModel; +using TerminalUI.Extensions; +using TerminalUI.Models; +using TerminalUI.ViewExtensions; + +namespace TerminalUI.Controls; + +public class Grid : View +{ + private const int ToBeCalculated = -1; + private readonly ObservableCollection _children = new(); + public ReadOnlyObservableCollection Children { get; } + public GridChildInitializer ChildInitializer { get; } + public ObservableCollection RowDefinitions { get; } = new(); + public ObservableCollection ColumnDefinitions { get; } = new(); + + public object? ColumnDefinitionsObject + { + get => ColumnDefinitions; + set + { + if (value is IEnumerable 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 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(this); + Children = new ReadOnlyObservableCollection(_children); + _children.CollectionChanged += (o, e) => + { + if (Attached) + { + if (e.NewItems?.OfType() 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 allWidth = stackalloc int[columns * RowDefinitions.Count]; + Span allHeight = stackalloc int[columns * RowDefinitions.Count]; + + foreach (var child in Children) + { + var childSize = child.GetRequestedSize(); + var positionExtension = child.GetExtension(); + 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 columnWidths = stackalloc int[columns]; + Span 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(); + 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 child) + { + child = base.AddChild(child); + _children.Add(child); + return child; + } + + public override TChild AddChild(TChild child, Func dataContextMapper) + where TDataContext : default + { + child = base.AddChild(child, dataContextMapper); + _children.Add(child); + return child; + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/GridChildInitializer.cs b/src/Library/TerminalUI/Controls/GridChildInitializer.cs new file mode 100644 index 0000000..7643db7 --- /dev/null +++ b/src/Library/TerminalUI/Controls/GridChildInitializer.cs @@ -0,0 +1,24 @@ +using System.Collections; + +namespace TerminalUI.Controls; + +public record ChildWithDataContextMapper(IView Child, Func DataContextMapper); + +public class GridChildInitializer : IEnumerable +{ + private readonly Grid _grid; + + public GridChildInitializer(Grid grid) + { + _grid = grid; + } + + public void Add(IView item) => _grid.AddChild(item); + + public void Add(ChildWithDataContextMapper item) + => _grid.AddChild(item.Child, item.DataContextMapper); + + public IEnumerator GetEnumerator() => _grid.Children.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/GridProperties.cs b/src/Library/TerminalUI/Controls/GridProperties.cs new file mode 100644 index 0000000..3742937 --- /dev/null +++ b/src/Library/TerminalUI/Controls/GridProperties.cs @@ -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); +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/IView.cs b/src/Library/TerminalUI/Controls/IView.cs index 8305d54..df9602a 100644 --- a/src/Library/TerminalUI/Controls/IView.cs +++ b/src/Library/TerminalUI/Controls/IView.cs @@ -7,10 +7,20 @@ namespace TerminalUI.Controls; public interface IView : INotifyPropertyChanged, IDisposableCollection { object? DataContext { get; set; } - Action 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 Extensions { get; } + + Action RenderMethod { get; set; } event Action Disposed; - void Render(Position position); + void Render(Position position, Size size); } public interface IView : IView @@ -28,4 +38,9 @@ public interface IView : IView TChild CreateChild(Func dataContextMapper) where TChild : IView, new(); + + TChild AddChild(TChild child) where TChild : IView; + + TChild AddChild(TChild child, Func dataContextMapper) + where TChild : IView; } \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/ListView.cs b/src/Library/TerminalUI/Controls/ListView.cs index a6c680c..01657f5 100644 --- a/src/Library/TerminalUI/Controls/ListView.cs +++ b/src/Library/TerminalUI/Controls/ListView.cs @@ -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 : View private object? _itemsSource; private ListViewItem[]? _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 : View public Func, 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; } } diff --git a/src/Library/TerminalUI/Controls/ListViewItem.cs b/src/Library/TerminalUI/Controls/ListViewItem.cs index e9ecdfc..29012eb 100644 --- a/src/Library/TerminalUI/Controls/ListViewItem.cs +++ b/src/Library/TerminalUI/Controls/ListViewItem.cs @@ -4,7 +4,13 @@ namespace TerminalUI.Controls; public class ListViewItem : ContentView { - 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 : ContentView + DataContext?.GetType().Name); } - ContentRendererMethod(position); + ContentRendererMethod(position, size); } } \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/Rectangle.cs b/src/Library/TerminalUI/Controls/Rectangle.cs new file mode 100644 index 0000000..eef5bbc --- /dev/null +++ b/src/Library/TerminalUI/Controls/Rectangle.cs @@ -0,0 +1,24 @@ +using PropertyChanged.SourceGenerator; +using TerminalUI.Color; +using TerminalUI.Models; + +namespace TerminalUI.Controls; + +public partial class Rectangle : View +{ + [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); + } + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Controls/TextBlock.cs b/src/Library/TerminalUI/Controls/TextBlock.cs index a23a65c..b380e44 100644 --- a/src/Library/TerminalUI/Controls/TextBlock.cs +++ b/src/Library/TerminalUI/Controls/TextBlock.cs @@ -1,4 +1,5 @@ using PropertyChanged.SourceGenerator; +using TerminalUI.Color; using TerminalUI.Extensions; using TerminalUI.Models; @@ -27,7 +28,9 @@ public partial class TextBlock : View 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); diff --git a/src/Library/TerminalUI/Controls/View.cs b/src/Library/TerminalUI/Controls/View.cs index e9fb209..f32f292 100644 --- a/src/Library/TerminalUI/Controls/View.cs +++ b/src/Library/TerminalUI/Controls/View.cs @@ -10,8 +10,30 @@ public abstract partial class View : IView { private readonly List _disposables = new(); [Notify] private T? _dataContext; - public Action 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 Extensions { get; } = new(); + public Action RenderMethod { get; set; } + public IApplicationContext? ApplicationContext { get; set; } public event Action? Disposed; protected List RerenderProperties { get; } = new(); @@ -20,22 +42,27 @@ public abstract partial class View : IView 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 : IView + DataContext?.GetType().Name); } - RenderMethod(position); + RenderMethod(position, size); } public TChild CreateChild() where TChild : IView, new() { - var child = new TChild - { - DataContext = DataContext, - ApplicationContext = ApplicationContext - }; + var child = new TChild(); + return AddChild(child); + } + + public TChild CreateChild(Func dataContextMapper) + where TChild : IView, new() + { + var child = new TChild(); + return AddChild(child, dataContextMapper); + } + + public virtual TChild AddChild(TChild child) where TChild : IView + { + child.DataContext = DataContext; + child.ApplicationContext = ApplicationContext; + var mapper = new DataContextMapper(this, d => child.DataContext = d); AddDisposable(mapper); child.AddDisposable(mapper); @@ -64,14 +102,12 @@ public abstract partial class View : IView return child; } - public TChild CreateChild(Func dataContextMapper) - where TChild : IView, new() + public virtual TChild AddChild(TChild child, Func dataContextMapper) + where TChild : IView { - var child = new TChild - { - DataContext = dataContextMapper(DataContext), - ApplicationContext = ApplicationContext - }; + child.DataContext = dataContextMapper(DataContext); + child.ApplicationContext = ApplicationContext; + var mapper = new DataContextMapper(this, d => child.DataContext = dataContextMapper(d)); AddDisposable(mapper); child.AddDisposable(mapper); diff --git a/src/Library/TerminalUI/EventLoop.cs b/src/Library/TerminalUI/EventLoop.cs index 6f0dfed..60073f7 100644 --- a/src/Library/TerminalUI/EventLoop.cs +++ b/src/Library/TerminalUI/EventLoop.cs @@ -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); } } diff --git a/src/Library/TerminalUI/Extensions/SpanExtensions.cs b/src/Library/TerminalUI/Extensions/SpanExtensions.cs new file mode 100644 index 0000000..f777c89 --- /dev/null +++ b/src/Library/TerminalUI/Extensions/SpanExtensions.cs @@ -0,0 +1,7 @@ +namespace TerminalUI.Extensions; + +public static class SpanExtensions +{ + public static T GetFromMatrix(this Span span, int x, int y, int width) => span[y * width + x]; + public static void SetToMatrix(this Span span, T value, int x, int y, int width) => span[y * width + x] = value; +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Extensions/ViewExtensions.cs b/src/Library/TerminalUI/Extensions/ViewExtensions.cs new file mode 100644 index 0000000..1523ada --- /dev/null +++ b/src/Library/TerminalUI/Extensions/ViewExtensions.cs @@ -0,0 +1,14 @@ +using TerminalUI.Controls; + +namespace TerminalUI.Extensions; + +public static class ViewExtensions +{ + public static T? GetExtension(this IView view) + => (T?) view.Extensions.FirstOrDefault(e => e is T); + + public static ChildWithDataContextMapper WithDataContextMapper( + this IView view, + Func dataContextMapper) + => new(view, dataContextMapper); +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Models/Position.cs b/src/Library/TerminalUI/Models/Position.cs index 2c40367..c845179 100644 --- a/src/Library/TerminalUI/Models/Position.cs +++ b/src/Library/TerminalUI/Models/Position.cs @@ -1,3 +1,3 @@ namespace TerminalUI.Models; -public record struct Position(int PosX, int PosY); \ No newline at end of file +public record struct Position(int X, int Y); \ No newline at end of file diff --git a/src/Library/TerminalUI/Models/Size.cs b/src/Library/TerminalUI/Models/Size.cs new file mode 100644 index 0000000..9be9239 --- /dev/null +++ b/src/Library/TerminalUI/Models/Size.cs @@ -0,0 +1,3 @@ +namespace TerminalUI.Models; + +public record Size(int Width, int Height); \ No newline at end of file diff --git a/src/Library/TerminalUI/Rendering/RenderingEngine.cs b/src/Library/TerminalUI/Rendering/RenderingEngine.cs new file mode 100644 index 0000000..fa0d27a --- /dev/null +++ b/src/Library/TerminalUI/Rendering/RenderingEngine.cs @@ -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(); + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/Traits/IContentRenderer.cs b/src/Library/TerminalUI/Traits/IContentRenderer.cs index d5106f2..b86ed5a 100644 --- a/src/Library/TerminalUI/Traits/IContentRenderer.cs +++ b/src/Library/TerminalUI/Traits/IContentRenderer.cs @@ -6,5 +6,5 @@ namespace TerminalUI.Traits; public interface IContentRenderer { IView? Content { get; set; } - Action ContentRendererMethod { get; set; } + Action ContentRendererMethod { get; set; } } \ No newline at end of file diff --git a/src/Library/TerminalUI/ViewExtensions/GridPositionExtension.cs b/src/Library/TerminalUI/ViewExtensions/GridPositionExtension.cs new file mode 100644 index 0000000..a4e2fc6 --- /dev/null +++ b/src/Library/TerminalUI/ViewExtensions/GridPositionExtension.cs @@ -0,0 +1,3 @@ +namespace TerminalUI.ViewExtensions; + +public record GridPositionExtension(int Row, int Column); \ No newline at end of file diff --git a/src/Server/FileTime.Server/Program.cs b/src/Server/FileTime.Server/Program.cs index a116692..22e8dd8 100644 --- a/src/Server/FileTime.Server/Program.cs +++ b/src/Server/FileTime.Server/Program.cs @@ -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(); } diff --git a/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs index 048e075..3835ab3 100644 --- a/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs +++ b/src/Tools/FileTime.Tools.Compression/CompressionUserCommandHandler.cs @@ -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[] {