diff --git a/TLD.sln b/TLD.sln index 245bd53..a779c60 100644 --- a/TLD.sln +++ b/TLD.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI", "src\TUI\TUI.csproj", "{F92C03F7-2A65-4D0A-9736-13E749AF6903}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Tests", "tests\WIdgets\TUI.Tests\TUI.Tests.csproj", "{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -16,5 +18,9 @@ Global {F92C03F7-2A65-4D0A-9736-13E749AF6903}.Debug|Any CPU.Build.0 = Debug|Any CPU {F92C03F7-2A65-4D0A-9736-13E749AF6903}.Release|Any CPU.ActiveCfg = Release|Any CPU {F92C03F7-2A65-4D0A-9736-13E749AF6903}.Release|Any CPU.Build.0 = Release|Any CPU + {2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/docs/RENDER.md b/docs/RENDER.md new file mode 100644 index 0000000..2afcf9a --- /dev/null +++ b/docs/RENDER.md @@ -0,0 +1,51 @@ +# RENDER + +```mermaid +classDiagram + %% Application layer + class ViewPort{ + + GetCanvas() + + string: Content + } + + %% Хранит данные + class Store { + Reload() + } + + %% Хранит разметку + class View { + Store + SetWidgets() + SetCanvas() + Render() + } + + %% Подгатавливает текст + class Painter { + + SetGrid() + + SetView() + } + + %% Composition + class Widget { + + State + ~ Bind() + ~ Render() + } + + class WidgetState + <> WidgetState + WidgetState : Loading + WidgetState : Mounted + WidgetState : BindingError + WidgetState : RenderError +``` + +```mermaid +flowchart LR + Change_Store --> Binding_Widgets + Binding_Widgets --> Render_Widgets + Render_Widgets --> Create_View + Create_View --> Fill_View_Port +``` \ No newline at end of file diff --git a/src/Dashboard/Dashboard.csproj b/src/Dashboard/Dashboard.csproj new file mode 100644 index 0000000..2b14c81 --- /dev/null +++ b/src/Dashboard/Dashboard.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/src/Dashboard/Program.cs b/src/Dashboard/Program.cs new file mode 100644 index 0000000..c462094 --- /dev/null +++ b/src/Dashboard/Program.cs @@ -0,0 +1,9 @@ +namespace Dashboard; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/CellsComponentBase.cs b/src/TUI/Components/Controls/CellsComponentBase.cs new file mode 100644 index 0000000..5cdde2a --- /dev/null +++ b/src/TUI/Components/Controls/CellsComponentBase.cs @@ -0,0 +1,37 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; + +namespace TUI.Components.Controls; + +public class CellsComponentBase : ComponentBase, IComponent +{ + private const int MaxCellWidth = 10; + + private readonly IEnumerable _cells; + + + public CellsComponentBase(IEnumerable cells) + { + _cells = cells; + } + + public void Render(Horizontal horizontal, Size size) + { + var content = new StringBuilder(); + foreach (var cell in _cells) + { + content.Append(Symbols.Space.Repeat(MaxCellWidth - cell.Width())); + content.Append(cell); + } + + // base.Render(content, position, size); + } + + public override Content Render() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Copyright.cs b/src/TUI/Components/Controls/Copyright.cs new file mode 100644 index 0000000..0cd2d2a --- /dev/null +++ b/src/TUI/Components/Controls/Copyright.cs @@ -0,0 +1,18 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls; + +public class Copyright : ComponentStaticBase +{ + protected override void RenderWithCache(StringBuilder builder) + { + builder.Append(Symbols.Copyright); + builder.Append(Symbols.Space); + builder.Append("Kolosov A. aka \"dnwSilver\"".Hint()); + builder.Append(Symbols.Space); + builder.Append(DateTime.Now.Year); + } +} diff --git a/src/TUI/Components/Controls/Dashboard.cs b/src/TUI/Components/Controls/Dashboard.cs new file mode 100644 index 0000000..ecdb115 --- /dev/null +++ b/src/TUI/Components/Controls/Dashboard.cs @@ -0,0 +1,68 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls; + +public class Dashboard : ComponentBase, IComponent +{ + private readonly string _title; + + public Dashboard(string title) + { + _title = title; + } + + public void Render(Horizontal horizontal, Size size) + { + var dashboardBuilder = new StringBuilder(); + + RenderTopLine(dashboardBuilder, size, _title); + RenderMiddleLine(dashboardBuilder, size); + RenderBottomLine(dashboardBuilder, size); + + // base.Render(dashboardBuilder, position, size); + } + + private static void RenderTopLine(StringBuilder dashboardBuilder, Size size, string title) + { + var halfWidth = (size.Width - title.Width() - (int)Indentation.BorderWidth * 2 - + (int)Indentation.Default * 2) / 2; + dashboardBuilder.Append(Symbols.Angles.LeftTop); + dashboardBuilder.Append(Symbols.Lines.Horizontal.Repeat(halfWidth)); + dashboardBuilder.AppendFormat("{0}{1}{0}", Symbols.Space.Repeat(Convert.ToInt32(Indentation.Default)), title); + dashboardBuilder.Append(Symbols.Lines.Horizontal.Repeat(halfWidth)); + dashboardBuilder.Append(Symbols.Angles.RightTop); + } + + private static void RenderMiddleLine(StringBuilder dashboardBuilder, Size size) + { + var dashboardHeight = size.Height - (int)Indentation.BorderWidth * 2; + + while (dashboardHeight > 0) + { + var bodyWidth = size.Width - (int)Indentation.BorderWidth * 2; + dashboardBuilder.Append(Symbols.Lines.Vertical); + dashboardBuilder.Append(Symbols.Space.Repeat(bodyWidth)); + dashboardBuilder.Append(Symbols.Lines.Vertical); + + dashboardHeight--; + } + } + + private static void RenderBottomLine(StringBuilder dashboardBuilder, Size size) + { + var width = size.Width - (int)Indentation.BorderWidth * 2; + dashboardBuilder.Append(Symbols.Angles.LeftBottom); + dashboardBuilder.Append(Symbols.Lines.Horizontal.Repeat(width)); + dashboardBuilder.Append(Symbols.Angles.RightBottom); + } + + public override Content Render() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/HeaderContainer.cs b/src/TUI/Components/Controls/HeaderContainer.cs new file mode 100644 index 0000000..abe8bef --- /dev/null +++ b/src/TUI/Components/Controls/HeaderContainer.cs @@ -0,0 +1,37 @@ +using TUI.Components.Controls.Statics; +using TUI.Components.Controls.Statics.Hints; +using TUI.Engine.Nodes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Containers; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls; + +public class HeaderContainer : IContainer +{ + public Orientation Orientation => Orientation.Horizontal; + + public Nodes Nodes + { + get + { + var versionHints = new VersionHints() + .Set(Indentation.Default); + + var tagsHints = new TagHints() + .Set(Indentation.Default); + + var appTypeHints = new AppTypeHints() + .Set(Indentation.Default); + + var hotkeysHints = new HotkeysHint() + .Set(Indentation.Default); + + var logo = new Logo() + .Set(Horizontal.Right) + .Set(left: Indentation.Default, bottom: Indentation.Default, right: Indentation.Default); + + return new Nodes { versionHints, tagsHints, appTypeHints, hotkeysHints, logo }; + } + } +} diff --git a/src/TUI/Components/Controls/Statics/Hints/AppTypeHints.cs b/src/TUI/Components/Controls/Statics/Hints/AppTypeHints.cs new file mode 100644 index 0000000..31520d0 --- /dev/null +++ b/src/TUI/Components/Controls/Statics/Hints/AppTypeHints.cs @@ -0,0 +1,28 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; +using TUI.UserInterface; + +namespace TUI.Components.Controls.Statics.Hints; + +public class AppTypeHints : ComponentStaticBase +{ + private readonly Dictionary _hints = new() + { + { Icons.NpmPackage, "package" }, + { Icons.DockerImage, "image" }, + { Icons.Site, "site" }, + { Icons.Api, "api" } + }; + + protected override void RenderWithCache(StringBuilder builder) + { + foreach (var hint in _hints) + { + builder.Append(hint.Key); + builder.Append(Symbols.Space); + builder.AppendLine(hint.Value.Hint()); + } + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Statics/Hints/HotkeysHint.cs b/src/TUI/Components/Controls/Statics/Hints/HotkeysHint.cs new file mode 100644 index 0000000..630177e --- /dev/null +++ b/src/TUI/Components/Controls/Statics/Hints/HotkeysHint.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls.Statics.Hints; + +public class HotkeysHint : ComponentStaticBase +{ + private readonly Dictionary _hints = new() + { + { "", "select prev" }, + { "", "select next" }, + { "󰬌", "toggle head" }, + { "󰬘", "quit" } + }; + + protected override void RenderWithCache(StringBuilder builder) + { + foreach (var hint in _hints) + { + builder.Append(hint.Key.Hint()); + builder.Append(Symbols.Space); + builder.AppendLine(hint.Value.Hint()); + } + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Statics/Hints/TagHints.cs b/src/TUI/Components/Controls/Statics/Hints/TagHints.cs new file mode 100644 index 0000000..54cb140 --- /dev/null +++ b/src/TUI/Components/Controls/Statics/Hints/TagHints.cs @@ -0,0 +1,28 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; +using TUI.UserInterface; + +namespace TUI.Components.Controls.Statics.Hints; + +public class TagHints : ComponentStaticBase +{ + private readonly Dictionary _hints = new() + { + { Icons.Auth, "Auth" }, + { Icons.NetworkPublic, "WWW" }, + { Icons.SEO, "SEO" }, + { Icons.GitLab, "VCS" } + }; + + protected override void RenderWithCache(StringBuilder builder) + { + foreach (var hint in _hints) + { + builder.Append(hint.Key); + builder.Append(Symbols.Space); + builder.AppendLine(hint.Value.Hint()); + } + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Statics/Hints/VersionHints.cs b/src/TUI/Components/Controls/Statics/Hints/VersionHints.cs new file mode 100644 index 0000000..fa5f3fd --- /dev/null +++ b/src/TUI/Components/Controls/Statics/Hints/VersionHints.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls.Statics.Hints; + +public class VersionHints : ComponentStaticBase +{ + private readonly Dictionary _hints = new() + { + { "󰎔", "too new".Info() }, + { "", "so good".Hint() }, + { "", "be nice".Main() }, + { "󰬟", "too old".Warning() } + }; + + protected override void RenderWithCache(StringBuilder builder) + { + foreach (var hint in _hints) + { + builder.Append(hint.Key.Hint()); + builder.Append(Symbols.Space); + builder.AppendLine(hint.Value); + } + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Statics/Logo.cs b/src/TUI/Components/Controls/Statics/Logo.cs new file mode 100644 index 0000000..41c54b8 --- /dev/null +++ b/src/TUI/Components/Controls/Statics/Logo.cs @@ -0,0 +1,20 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Theme; + +namespace TUI.Components.Controls.Statics; + +public class Logo : ComponentStaticBase +{ + protected override void RenderWithCache(StringBuilder builder) + { + builder.Append($" {"╭━━━━┳╮".Main()}{"╱╱".Hint()}{"╭━━━╮".Main()}").Append(Symbols.LineBreak); + builder.Append($" {"┃╭╮╭╮┃┃".Main()}{"╱╱".Hint()}{"╰╮╭╮┃".Main()}").Append(Symbols.LineBreak); + builder.Append($" {"╰╯┃┃╰┫┃".Main()}{"╱╱╱".Hint()}{"┃┃┃┃".Main()}").Append(Symbols.LineBreak); + builder.Append($" {"╱╱".Hint()}{"┃┃".Main()}{"╱".Hint()}{"┃┃".Main()}{"╱".Hint()}{"╭╮┃┃┃┃".Main()}") + .Append(Symbols.LineBreak); + builder.Append($" {"╱╱╱".Hint()}{"┃┃".Main()}{"╱".Hint()}{"┃╰━╯┣╯╰╯┃".Main()}").Append(Symbols.LineBreak); + builder.Append($"{"╱╱╱╱".Hint()}{"╰╯".Main()}{"╱".Hint()}{"╰━━━┻━━━╯".Main()}").Append(Symbols.LineBreak); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Controls/Tag.cs b/src/TUI/Components/Controls/Tag.cs new file mode 100644 index 0000000..6969993 --- /dev/null +++ b/src/TUI/Components/Controls/Tag.cs @@ -0,0 +1,66 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Rendering; +using TUI.Engine.Theme; +using TUI.UserInterface; + +namespace TUI.Components.Controls; + +public class Tag : ComponentBase +{ + private IEnumerable _tags; + private string _gitType; + + public Tag(IRenderingEngine renderingEngine) + { + } + + public void Bind(IEnumerable tags, string gitType) + { + _tags = tags; + _gitType = gitType; + } + + public void Render(Horizontal horizontal, Size size) + { + var tagBuilder = new StringBuilder(); + + tagBuilder.Append(GetGitTypeImage(_gitType)); + tagBuilder.Append(Symbols.Space); + tagBuilder.Append(_tags.Have("public") ? Icons.NetworkPublic : Icons.NetworkPrivate); + tagBuilder.Append(Symbols.Space); + tagBuilder.Append(_tags.Have("seo") ? Icons.SEO : Icons.SEO.Disable()); + tagBuilder.Append(Symbols.Space); + tagBuilder.Append(_tags.Have("auth") ? Icons.Auth : Icons.Auth.Disable()); + tagBuilder.Append(Symbols.Space); + tagBuilder.Append(GetApplicationType()); + tagBuilder.Append(Symbols.Space); + + // base.Render(tagBuilder, position, size); + } + + private string GetApplicationType() + { + foreach (var application in Icons.Applications) + if (_tags.Have(application.Value)) + return application.Key; + + return Icons.Undefined; + } + + private static char GetGitTypeImage(string gitType) => + gitType switch + { + "gitlab" => Symbols.GitLab, + "github" => Symbols.GitHub, + _ => Symbols.Git + }; + + public override Content Render() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Layouts/DashboardLayout.cs b/src/TUI/Components/Layouts/DashboardLayout.cs new file mode 100644 index 0000000..95ab0a1 --- /dev/null +++ b/src/TUI/Components/Layouts/DashboardLayout.cs @@ -0,0 +1,36 @@ +using TUI.Engine.Nodes; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Nodes.Containers; + +namespace TUI.Components.Layouts; + +public class DashboardLayout : IContainer +{ + public Orientation Orientation { get; } = Orientation.Vertical; + + private INode _header; + private INode _footer; + + public Nodes Nodes => + new() + { + _header, _footer + }; + + public DashboardLayout AddHeader(IContainer header) + { + _header = header; + return this; + } + + public DashboardLayout AddFooter(IComponent footer) + { + _footer = footer; + return this; + } + + public string Render() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Components/Views/DependenciesView.cs b/src/TUI/Components/Views/DependenciesView.cs new file mode 100644 index 0000000..e74bd4e --- /dev/null +++ b/src/TUI/Components/Views/DependenciesView.cs @@ -0,0 +1,160 @@ +using TUI.Components.Controls; +using TUI.Domain; +using TUI.Engine; +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; + +namespace TUI.Components.Views; + +public class DependenciesView : ComponentBase, IComponent +{ + private const string ViewName = "Dependencies"; + + private DevelopmentStack _developmentStack; + + public void Bind(DevelopmentStack developmentStack) + { + _developmentStack = developmentStack; + } + + public void Render(Horizontal horizontal, Size size) + { + var dashboardTitle = _developmentStack.Icon + Symbols.Space + ViewName; + var dashboard = new Dashboard(dashboardTitle); + + Add(dashboard); + } + + // private const int TitleWidth = 25; + // private const int ColumnWidth = 10; + + // private readonly DashboardControl _dashboard = new(); + + // public bool IsFocused + // { + // get => _dashboard.IsFocused; + // set => _dashboard.IsFocused = value; + // } + + // public void Render(ProjectDto projectDto, ControlPosition position) + // { + // _dashboard.Render(projectDto.Icon + " Dependencies", position); + // var header = projectDto.Dependencies.Select(GetConventionVersion).ToArray(); + // var rows = projectDto.Sources.Select(GetTitle).ToArray(); + // + // var tablePosition = new ControlPosition( + // position.Left + Theme.BorderWidth, + // position.Top + Theme.BorderWidth); + // + // var tableProps = new TableProps(header, rows, TitleWidth, ColumnWidth); + // _table.Render(tableProps, tablePosition); + // + // for (var rowId = 0; rowId < rows.Length; rowId++) + // { + // var actualDependencies = GetDependencies(projectDto.Sources[rowId], projectDto.Dependencies); + // _table.RenderRow(rowId + 1, rows[rowId] + actualDependencies); + // } + // } + + // private static string GetDependencies(SourceDto sourceDto, IEnumerable conventionDependencies) + // { + // try + // { + // var package = DownloadPackage(sourceDto); + // + // return string.Join("", + // conventionDependencies + // .Select(dependency => GetVersion(dependency, package)) + // .Select(RenderCurrentVersion)); + // } + // catch (HttpRequestException exception) + // { + // switch (exception.StatusCode) + // { + // case HttpStatusCode.BadRequest: + // return " Request have errors.".Pastel(Palette.ErrorColor); + // case HttpStatusCode.Forbidden: + // return " Not enough rights.".Pastel(Palette.ErrorColor); + // case HttpStatusCode.NotFound: + // return " Repository or branch master not found.".Pastel(Palette.ErrorColor); + // } + // + // throw; + // } + // catch (Exception exception) + // { + // Debugger.Break(); + // return "󰋔 We tried to send a request but couldn't. Check your configuration.".Pastel(Palette.ErrorColor); + // } + // } + // + // private static string GetVersion(DependencyDto dependency, Package package) + // { + // var currentVersion = package.ParseVersion(dependency.Name); + // + // if (currentVersion == null) return Icons.NotFound; + // + // var conventionVersion = dependency.Version?.ToVersion(); + // return PaintingVersion(currentVersion, conventionVersion); + // } + // + // private static string PaintingVersion(Version current, Version? convention) + // { + // var textVersion = current.ToString(); + // + // if (current > convention) return textVersion.Info(); + // + // if (current < convention) + // return current.Major == convention.Major ? textVersion.Primary() : textVersion.Warning(); + // + // return textVersion.Hint(); + // } + // + // private static string GetConventionVersion(DependencyDto dependencyDto) + // { + // return dependencyDto.Icon.Pastel(dependencyDto.Color) + dependencyDto.Version.Primary(); + // } + // + // private static string RenderCurrentVersion(string version) + // { + // var versionWidth = version.Width(); + // if (versionWidth == 0) return ' '.Repeat(ColumnWidth - 1) + Icons.NotFound.Hint(); + // + // return ' '.Repeat(ColumnWidth - versionWidth) + version; + // } + // + // private static string GetTitle(SourceDto sourceDto) + // { + // var title = ""; + // + // title += RenderPadding(); + // title += RenderTags(sourceDto); + // if (title.Width() + sourceDto.Name.Length + Theme.Padding <= TitleWidth) + // { + // title += sourceDto.Name; + // } + // else + // { + // var maxNameWidth = TitleWidth - title.Width() - Theme.Padding; + // title += $"{sourceDto.Name[..(maxNameWidth - 1)]}{"#".Hint()}"; + // } + // + // title += RenderPadding(); + // return $"{title}{' '.Repeat(TitleWidth - title.Width())}"; + // } + // + // public void Next() + // { + // _table.Next(); + // } + // + // public void Previous() + // { + // _table.Previous(); + // } + public override Content Render() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Copyright.cs b/src/TUI/Controls/Copyright.cs deleted file mode 100644 index 5faf29f..0000000 --- a/src/TUI/Controls/Copyright.cs +++ /dev/null @@ -1,16 +0,0 @@ -using TUI.UserInterface; - - -namespace TUI.Controls; - -public class Copyright : IControl -{ - public void Render(string author, Position position, int? height = 0) - { - const string icon = "© "; - Console.SetCursorPosition(position.Left - icon.Width(), position.Top); - - var copyright = $"{icon}{author}".Hint(); - Console.Write(copyright); - } -} \ No newline at end of file diff --git a/src/TUI/Controls/Dashboard.cs b/src/TUI/Controls/Dashboard.cs deleted file mode 100644 index 581061e..0000000 --- a/src/TUI/Controls/Dashboard.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Text; -using TUI.UserInterface; - - -namespace TUI.Controls; - -public class Dashboard : IControl -{ - public bool IsFocused { get; set; } - - public void Render(string title, Position position, int? height = 0) - { - Console.SetCursorPosition(position.Left, position.Top); - - RenderTopLine(title, IsFocused); - - var marginTop = position.Top; - - var dashboardHeight = height == 0 ? Console.WindowHeight - marginTop : height + Theme.Padding * 2; - - for (var top = marginTop; - top < dashboardHeight + marginTop - Theme.BorderWidth * 2 - Theme.Padding * 2; - top++) - RenderMiddleLine(IsFocused); - - RenderBottomLine(IsFocused); - } - - private static void RenderMiddleLine(bool isFocused) - { - Console.Write("│".Primary(isFocused)); - Console.Write(new string(' ', Console.WindowWidth - Theme.BorderWidth * 2)); - Console.WriteLine("│".Primary(isFocused)); - } - - private static void RenderBottomLine(bool isFocused) - { - var lineWidth = Console.WindowWidth - Theme.BorderWidth * 2; - Console.Write("└".Primary(isFocused)); - Console.Write('─'.Repeat(lineWidth).Primary(isFocused)); - Console.WriteLine("┘".Primary(isFocused)); - } - - private static void RenderTopLine(string title, bool isFocused) - { - var lineWidth = - (Console.WindowWidth - title.Width() - Theme.BorderWidth * 2 - Theme.Padding * 2) / - 2; - - var topLine = new StringBuilder(); - topLine.Append("┌"); - topLine.Append('─'.Repeat(lineWidth)); - topLine.AppendFormat("{0}{1}{0}", ' '.Repeat(Theme.Padding), title); - if (title.Width() % 2 == 1) topLine.Append('─'); - - topLine.Append('─'.Repeat(lineWidth)); - topLine.Append("┐"); - Console.WriteLine(topLine.ToString().Primary(isFocused)); - } -} \ No newline at end of file diff --git a/src/TUI/Controls/Display.cs b/src/TUI/Controls/Display.cs deleted file mode 100644 index 3bff348..0000000 --- a/src/TUI/Controls/Display.cs +++ /dev/null @@ -1,97 +0,0 @@ -using TUI.Dashboards; -using TUI.Domain; -using TUI.UserInterface; - - -namespace TUI.Controls; - -public class Display -{ - public readonly CommandLine CommandLine; - - public readonly Copyright Copyright; - - public readonly DependencyDashboard DependencyDashboard; - public readonly Header Header; - private bool _commandLineInDisplay; - - private Project _currentProject; - private bool _headerInDisplay = true; - - public Display() - { - Header = new Header(); - Copyright = new Copyright(); - CommandLine = new CommandLine(); - DependencyDashboard = new DependencyDashboard(); - Render(); - } - - public string FocusedElement { get; set; } = ""; - - public void OpenDeps(Project project) - { - _currentProject = project; - var dashboardPosition = new Position(0, Header.Height); - DependencyDashboard.IsFocused = true; - CommandLine.IsFocused = false; - DependencyDashboard.Render(_currentProject, dashboardPosition); - } - - private void ResizeDependencies() - { - var topPosition = 0; - topPosition += _commandLineInDisplay ? CommandLine.Height : 0; - topPosition += _headerInDisplay ? Header.Height : 0; - var dashboardPosition = new Position(0, topPosition); - DependencyDashboard.Render(_currentProject, dashboardPosition); - } - - public void Render() - { - var headerPosition = new Position(0, 0); - Header.Render(headerPosition); - - const string copyrightText = "Kolosov Aleksandr"; - var copyrightPosition = new Position( - Console.WindowWidth - copyrightText.Width(), - Console.WindowHeight); - Copyright.Render(copyrightText, copyrightPosition); - } - - public void ToggleHeader() - { - _headerInDisplay = !_headerInDisplay; - if (_headerInDisplay) - { - var headerPosition = new Position(0, 0); - Header.Render(headerPosition); - } - - ResizeDependencies(); - } - - public void Next() - { - DependencyDashboard.Next(); - } - - public void Previous() - { - DependencyDashboard.Previous(); - } - - public void OpenCommandLine() - { - var commandLinePosition = new Position(0, _headerInDisplay ? Header.Height : 0); - CommandLine.IsFocused = true; - DependencyDashboard.IsFocused = false; - FocusedElement = nameof(CommandLine); - CommandLine.Render(commandLinePosition); - _commandLineInDisplay = true; - ResizeDependencies(); - Console.SetCursorPosition(commandLinePosition.Left + Theme.Padding + Theme.BorderWidth + 2, - commandLinePosition.Top + Theme.BorderWidth); - Console.CursorVisible = true; - } -} \ No newline at end of file diff --git a/src/TUI/Controls/IControl.cs b/src/TUI/Controls/IControl.cs deleted file mode 100644 index 231c82b..0000000 --- a/src/TUI/Controls/IControl.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace TUI.Controls; - -public interface IControl -{ - void Render(Position position); -} - -public interface IControl -{ - // bool IsFocused { get; } - - void Render(TProps props, Position position, int? height); -} \ No newline at end of file diff --git a/src/TUI/Controls/Position.cs b/src/TUI/Controls/Position.cs deleted file mode 100644 index d9200b9..0000000 --- a/src/TUI/Controls/Position.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace TUI.Controls; - -public record Position(int Left, int Top); \ No newline at end of file diff --git a/src/TUI/Controls/Table.cs b/src/TUI/Controls/Table.cs deleted file mode 100644 index 1bf1249..0000000 --- a/src/TUI/Controls/Table.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Pastel; -using TUI.UserInterface; - - -namespace TUI.Controls; - -public record TableProps(IEnumerable HeaderCells, - IEnumerable Rows, - int TitleWidth, - int ColumnWidth); - -public class Table : IControl -{ - private readonly Dictionary _rows = new(); - private Position _position; - private int _selectedRowId; - - public void Render(TableProps props, Position position, int? height = 0) - { - _position = position; - Console.SetCursorPosition(_position.Left, _position.Top); - - Console.Write(' '.Repeat(props.TitleWidth)); - foreach (var headerCell in props.HeaderCells) - Console.Write(' '.Repeat(props.ColumnWidth - headerCell.Width()) + headerCell); - } - - public void RenderRow(int rowId, string rowText, string? bgColor = default) - { - var padRight = ' '.Repeat(Console.WindowWidth - rowText.Width() - Theme.BorderWidth * 2); - _rows[rowId] = rowText + padRight; - - Console.SetCursorPosition(_position.Left, _position.Top + rowId); - Console.Write(string.IsNullOrEmpty(bgColor) ? rowText : rowText.PastelBg(bgColor)); - } - - public void Next() - { - if (_selectedRowId >= _rows.Count) return; - - RemoveHoverFromCurrentRow(); - RenderRow(++_selectedRowId, _rows[_selectedRowId], Palette.HoverColor); - } - - private void RemoveHoverFromCurrentRow() - { - if (_rows.TryGetValue(_selectedRowId, out var row)) RenderRow(_selectedRowId, row); - } - - public void Previous() - { - if (_selectedRowId == 0) Next(); - - if (_selectedRowId == 1) return; - - RemoveHoverFromCurrentRow(); - RenderRow(--_selectedRowId, _rows[_selectedRowId], Palette.HoverColor); - } -} \ No newline at end of file diff --git a/src/TUI/Dashboards/DependencyDashboard.cs b/src/TUI/Dashboards/DependencyDashboard.cs deleted file mode 100644 index 96fba08..0000000 --- a/src/TUI/Dashboards/DependencyDashboard.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Text.Json; -using Pastel; -using TUI.Controls; -using TUI.Domain; -using TUI.Settings; -using TUI.UserInterface; - - -namespace TUI.Dashboards; - -public class DependencyDashboard : IControl -{ - private const int TitleWidth = 25; - private const int ColumnWidth = 10; - - private readonly static Dictionary Packages = new(); - private readonly Dashboard _dashboard = new(); - - private readonly Table _table = new(); - - public bool IsFocused - { - get => _dashboard.IsFocused; - set => _dashboard.IsFocused = value; - } - - public void Render(Project project, Position position, int? height = 0) - { - _dashboard.Render(project.Icon + " Dependencies", position); - var header = project.Dependencies.Select(GetConventionVersion).ToArray(); - var rows = project.Sources.Select(GetTitle).ToArray(); - - var tablePosition = new Position( - position.Left + Theme.BorderWidth, - position.Top + Theme.BorderWidth); - - var tableProps = new TableProps(header, rows, TitleWidth, ColumnWidth); - - _table.Render(tableProps, tablePosition); - - for (var rowId = 0; rowId < rows.Length; rowId++) - { - var actualDependencies = GetDependencies(project.Sources[rowId], project.Dependencies); - _table.RenderRow(rowId + 1, rows[rowId] + actualDependencies); - } - } - - private static string GetDependencies(SourceDto sourceDto, IEnumerable conventionDependencies) - { - try - { - var package = DownloadPackage(sourceDto); - - return string.Join("", - conventionDependencies - .Select(dependency => GetVersion(dependency, package)) - .Select(RenderCurrentVersion)); - } - catch (HttpRequestException exception) - { - switch (exception.StatusCode) - { - case HttpStatusCode.BadRequest: - return " Request have errors.".Pastel(Palette.ErrorColor); - case HttpStatusCode.Forbidden: - return " Not enough rights.".Pastel(Palette.ErrorColor); - case HttpStatusCode.NotFound: - return " Repository or branch master not found.".Pastel(Palette.ErrorColor); - } - - throw; - } - catch (Exception exception) - { - Debugger.Break(); - return "󰋔 We tried to send a request but couldn't. Check your configuration.".Pastel(Palette.ErrorColor); - } - } - - - private static string GetVersion(DependencyDto dependency, Package package) - { - var currentVersion = package.ParseVersion(dependency.Name); - - if (currentVersion == null) return Icons.NotFound; - - var conventionVersion = dependency.Version?.ToVersion(); - return PaintingVersion(currentVersion, conventionVersion); - } - - private static string PaintingVersion(Version current, Version? convention) - { - var textVersion = current.ToString(); - - if (current > convention) return textVersion.Info(); - - if (current < convention) - return current.Major == convention.Major ? textVersion.Primary() : textVersion.Warning(); - - return textVersion.Hint(); - } - - private static Package DownloadPackage(SourceDto sourceDto) - { - var endpoint = sourceDto.Tags.Have("gitlab") ? GetGitlabEndpoint(sourceDto) : sourceDto.Repo; - if (Packages.TryGetValue(endpoint, out var downloadPackage)) return downloadPackage; - - using HttpClient client = new(); - var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult(); - var package = JsonSerializer.Deserialize(json); - Packages.Add(endpoint, package); - return package; - } - - private static string GetGitlabEndpoint(SourceDto sourceDto) - { - var token = Environment.GetEnvironmentVariable("TLD_GITLAB_PAT"); - return $"{sourceDto.Repo}/api/v4/projects/{sourceDto.ProjectId}/repository/files/package.json/raw?" + - $"private_token={token}&ref=dev"; - } - - private static string GetConventionVersion(DependencyDto dependencyDto) - { - return dependencyDto.Icon.Pastel(dependencyDto.Color) + dependencyDto.Version.Primary(); - } - - private static string RenderCurrentVersion(string version) - { - var versionWidth = version.Width(); - if (versionWidth == 0) return ' '.Repeat(ColumnWidth - 1) + Icons.NotFound.Hint(); - - return ' '.Repeat(ColumnWidth - versionWidth) + version; - } - - private static string GetTitle(SourceDto sourceDto) - { - var title = ""; - - title += RenderPadding(); - title += RenderTags(sourceDto); - if (title.Width() + sourceDto.Name.Length + Theme.Padding <= TitleWidth) - { - title += sourceDto.Name; - } - else - { - var maxNameWidth = TitleWidth - title.Width() - Theme.Padding; - title += $"{sourceDto.Name[..(maxNameWidth - 1)]}{"#".Hint()}"; - } - - title += RenderPadding(); - return $"{title}{' '.Repeat(TitleWidth - title.Width())}"; - } - - private static string RenderPadding() - { - return new string(' ', Theme.Padding); - } - - private static string RenderTags(SourceDto sourceDto) - { - var tags = ""; - tags += GetGitApplication(sourceDto); - tags += " "; - tags += sourceDto.Tags.Have("public") ? Icons.NetworkPublic : Icons.NetworkPrivate; - tags += " "; - tags += sourceDto.Tags.Have("seo") ? Icons.SEO : Icons.SEO.Disable(); - tags += " "; - tags += sourceDto.Tags.Have("auth") ? Icons.Auth : Icons.Auth.Disable(); - tags += " "; - tags += GetApplicationType(sourceDto); - tags += " "; - - return tags; - } - - private static string GetApplicationType(SourceDto sourceDto) - { - foreach (var application in Icons.Applications) - if (sourceDto.Tags.Have(application.Value)) - return application.Key; - - return Icons.Undefined; - } - - private static string GetGitApplication(SourceDto sourceDto) - { - return sourceDto.Repo switch - { - { } url when url.Contains("gitlab") => Icons.GitLab, - { } url when url.Contains("github") => Icons.GitHub, - _ => Icons.Git - }; - } - - public void Next() - { - _table.Next(); - } - - public void Previous() - { - _table.Previous(); - } -} \ No newline at end of file diff --git a/src/TUI/Domain/DevelopmentStack.cs b/src/TUI/Domain/DevelopmentStack.cs new file mode 100644 index 0000000..e24631b --- /dev/null +++ b/src/TUI/Domain/DevelopmentStack.cs @@ -0,0 +1,3 @@ +namespace TUI.Domain; + +public record DevelopmentStack(string Name, char Icon); \ No newline at end of file diff --git a/src/TUI/Domain/Project.cs b/src/TUI/Domain/ProjectDto.cs similarity index 70% rename from src/TUI/Domain/Project.cs rename to src/TUI/Domain/ProjectDto.cs index dc08f5b..df48aa4 100644 --- a/src/TUI/Domain/Project.cs +++ b/src/TUI/Domain/ProjectDto.cs @@ -1,21 +1,27 @@ +using System.Runtime.Serialization; using TUI.Settings; using YamlDotNet.Serialization; namespace TUI.Domain; +[DataContract] [YamlSerializable] -public class Project +public class ProjectDto { [YamlMember] + [DataMember] public string Icon { get; set; } [YamlMember] + [DataMember] public string Name { get; set; } [YamlMember] + [DataMember] public DependencyDto[] Dependencies { get; set; } [YamlMember] + [DataMember] public IList Sources { get; set; } } \ No newline at end of file diff --git a/src/TUI/Domain/Settings.cs b/src/TUI/Domain/Settings.cs index 41085f8..6f03fe8 100644 --- a/src/TUI/Domain/Settings.cs +++ b/src/TUI/Domain/Settings.cs @@ -8,7 +8,7 @@ namespace TUI.Domain; public class Settings { [YamlMember] - public Project[] Projects { get; set; } + public ProjectDto[] Projects { get; set; } public static Settings Init() { diff --git a/src/TUI/Engine/Helper.cs b/src/TUI/Engine/Helper.cs new file mode 100644 index 0000000..8c7a1ad --- /dev/null +++ b/src/TUI/Engine/Helper.cs @@ -0,0 +1,46 @@ +using Pastel; +using TUI.Engine.Nodes; +using TUI.Engine.Nodes.Attributes; + +namespace TUI.Engine; + +public static class Helper +{ + private static readonly Queue Colors = new(); + + static Helper() + { + Colors.Enqueue(ConsoleColor.DarkYellow); + Colors.Enqueue(ConsoleColor.DarkMagenta); + Colors.Enqueue(ConsoleColor.DarkGreen); + Colors.Enqueue(ConsoleColor.DarkCyan); + Colors.Enqueue(ConsoleColor.DarkBlue); + Colors.Enqueue(ConsoleColor.DarkRed); + Colors.Enqueue(ConsoleColor.Cyan); + Colors.Enqueue(ConsoleColor.Yellow); + } + + public static void ShowBackground(NodePosition position, Size size) + { + return; + var color = Colors.Dequeue(); + var top = position.Top; + var height = 0; + + while (height < size.Height) + { + var left = position.Left; + var width = 0; + while (width < size.Width) + { + Console.SetCursorPosition(left, top); + Console.Write(Symbols.Space.ToString().PastelBg(color)); + width++; + left++; + } + + height++; + top++; + } + } +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Alignments/Alignment.cs b/src/TUI/Engine/Nodes/Attributes/Alignments/Alignment.cs new file mode 100644 index 0000000..1ddb95c --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Alignments/Alignment.cs @@ -0,0 +1,3 @@ +namespace TUI.Engine.Nodes.Attributes.Alignments; + +public record Alignment(Horizontal Horizontal, Vertical Vertical); \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Alignments/Horizontal.cs b/src/TUI/Engine/Nodes/Attributes/Alignments/Horizontal.cs new file mode 100644 index 0000000..cbc839a --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Alignments/Horizontal.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Nodes.Attributes.Alignments; + +public enum Horizontal +{ + Left = 0, + Center = 1, + Right = 2, +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Alignments/IWithAlignment.cs b/src/TUI/Engine/Nodes/Attributes/Alignments/IWithAlignment.cs new file mode 100644 index 0000000..56067a2 --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Alignments/IWithAlignment.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Nodes.Attributes.Alignments; + +public interface IWithAlignment where TNode : INode +{ + public Alignment Alignment { get; } + + public TNode Set(Horizontal horizontal = Horizontal.Left, Vertical vertical = Vertical.Top); +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Alignments/Vertical.cs b/src/TUI/Engine/Nodes/Attributes/Alignments/Vertical.cs new file mode 100644 index 0000000..f03c6fa --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Alignments/Vertical.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Nodes.Attributes.Alignments; + +public enum Vertical +{ + Top, + Center, + Bottom, +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Paddings/IWithPadding.cs b/src/TUI/Engine/Nodes/Attributes/Paddings/IWithPadding.cs new file mode 100644 index 0000000..493fc45 --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Paddings/IWithPadding.cs @@ -0,0 +1,17 @@ +using TUI.Engine.Theme; + +namespace TUI.Engine.Nodes.Attributes.Paddings; + +public interface IWithPadding where TNode : INode +{ + public Padding? Padding { get; } + + public TNode Set(Level padding); + + public TNode Set( + Level? left = Level.None, + Level? top = Level.None, + Level? right = Level.None, + Level? bottom = Level.None + ); +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Paddings/Padding.cs b/src/TUI/Engine/Nodes/Attributes/Paddings/Padding.cs new file mode 100644 index 0000000..f7ccf52 --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Paddings/Padding.cs @@ -0,0 +1,15 @@ +using TUI.Engine.Theme; + +namespace TUI.Engine.Nodes.Attributes.Paddings; + +public record Padding( + Level? Left = Level.None, + Level? Top = Level.None, + Level? Right = Level.None, + Level? Bottom = Level.None +) +{ + public Padding(Level padding) : this(Left: padding, Top: padding, Right: padding, Bottom: padding) + { + } +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Attributes/Size.cs b/src/TUI/Engine/Nodes/Attributes/Size.cs new file mode 100644 index 0000000..d0c7689 --- /dev/null +++ b/src/TUI/Engine/Nodes/Attributes/Size.cs @@ -0,0 +1,6 @@ +namespace TUI.Engine.Nodes.Attributes; + +public record Size(int Width, int Height) +{ + public override string ToString() => $"W[{Width}] H[{Height}]"; +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Components/ComponentBase.cs b/src/TUI/Engine/Nodes/Components/ComponentBase.cs new file mode 100644 index 0000000..05caf8f --- /dev/null +++ b/src/TUI/Engine/Nodes/Components/ComponentBase.cs @@ -0,0 +1,45 @@ +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Attributes.Paddings; +using TUI.Engine.Theme; + +namespace TUI.Engine.Nodes.Components; + +public abstract class ComponentBase : List, IComponent +{ + public abstract Content Render(); + + #region Alignments + + public Alignment Alignment { get; private set; } = new(Horizontal.Center, Vertical.Top); + + public IComponent Set(Horizontal horizontal = Horizontal.Left, Vertical vertical = Vertical.Top) + { + Alignment = new Alignment(horizontal, vertical); + return this; + } + + #endregion + + #region Paddings + + public Padding Padding { get; private set; } = new(Level.None); + + public IComponent Set(Level padding) + { + Padding = new Padding(padding); + return this; + } + + public IComponent Set( + Level? left = Level.None, + Level? top = Level.None, + Level? right = Level.None, + Level? bottom = Level.None + ) + { + Padding = new Padding(left, top, right, bottom); + return this; + } + + #endregion +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Components/ComponentStaticBase.cs b/src/TUI/Engine/Nodes/Components/ComponentStaticBase.cs new file mode 100644 index 0000000..6e9a3ce --- /dev/null +++ b/src/TUI/Engine/Nodes/Components/ComponentStaticBase.cs @@ -0,0 +1,24 @@ +using System.Text; + +namespace TUI.Engine.Nodes.Components; + +public abstract class ComponentStaticBase : ComponentBase +{ + private Content? _cache; + + protected abstract void RenderWithCache(StringBuilder builder); + + public override Content Render() + { + if (_cache is not null) + { + return _cache; + } + + var builder = new StringBuilder(); + RenderWithCache(builder); + _cache = new Content(builder.ToString()); + + return _cache; + } +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Components/Content.cs b/src/TUI/Engine/Nodes/Components/Content.cs new file mode 100644 index 0000000..d357bb1 --- /dev/null +++ b/src/TUI/Engine/Nodes/Components/Content.cs @@ -0,0 +1,26 @@ +using TUI.Engine.Nodes.Attributes; + +namespace TUI.Engine.Nodes.Components; + +public sealed class Content : IEnumerable +{ + private IEnumerable ContentRows { get; } + + public Content(string content) => ContentRows = content.Split(Symbols.LineBreak); + + public IEnumerator GetEnumerator() => ContentRows.GetEnumerator(); + + public IEnumerable Rows(int maxWidth, int maxHeight) => + ContentRows.Where(row => maxWidth >= row.Width()).Take(maxHeight).ToArray(); + + public Size GetSize() + { + var width = ContentRows.Select(row => row.Width()).DefaultIfEmpty(0).Max(); + var height = ContentRows.Count(); + return new Size(width, height); + } + + public override string ToString() => string.Join(Symbols.LineBreak, ContentRows); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Components/IComponent.cs b/src/TUI/Engine/Nodes/Components/IComponent.cs new file mode 100644 index 0000000..4a841cc --- /dev/null +++ b/src/TUI/Engine/Nodes/Components/IComponent.cs @@ -0,0 +1,11 @@ +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Attributes.Paddings; + +namespace TUI.Engine.Nodes.Components; + +public interface IComponent : INode, + IWithAlignment, + IWithPadding +{ + Content Render(); +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Containers/IContainer.cs b/src/TUI/Engine/Nodes/Containers/IContainer.cs new file mode 100644 index 0000000..aac4955 --- /dev/null +++ b/src/TUI/Engine/Nodes/Containers/IContainer.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Nodes.Containers; + +public interface IContainer : INode +{ + public Orientation Orientation { get; } + + public Nodes Nodes { get; } +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Containers/Orientation.cs b/src/TUI/Engine/Nodes/Containers/Orientation.cs new file mode 100644 index 0000000..f53ba11 --- /dev/null +++ b/src/TUI/Engine/Nodes/Containers/Orientation.cs @@ -0,0 +1,7 @@ +namespace TUI.Engine.Nodes.Containers; + +public enum Orientation +{ + Horizontal, + Vertical, +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/INode.cs b/src/TUI/Engine/Nodes/INode.cs new file mode 100644 index 0000000..52874c7 --- /dev/null +++ b/src/TUI/Engine/Nodes/INode.cs @@ -0,0 +1,5 @@ +namespace TUI.Engine.Nodes; + +public interface INode +{ +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/NodePosition.cs b/src/TUI/Engine/Nodes/NodePosition.cs new file mode 100644 index 0000000..b0c9cf5 --- /dev/null +++ b/src/TUI/Engine/Nodes/NodePosition.cs @@ -0,0 +1,6 @@ +namespace TUI.Engine.Nodes; + +public record NodePosition(int Left, int Top) +{ + public override string ToString() => $"L[{Left}] T[{Top}]"; +} \ No newline at end of file diff --git a/src/TUI/Engine/Nodes/Nodes.cs b/src/TUI/Engine/Nodes/Nodes.cs new file mode 100644 index 0000000..0f58be7 --- /dev/null +++ b/src/TUI/Engine/Nodes/Nodes.cs @@ -0,0 +1,5 @@ +namespace TUI.Engine.Nodes; + +public class Nodes : List +{ +} \ No newline at end of file diff --git a/src/TUI/Engine/Rendering/ConsoleRenderingEngine.cs b/src/TUI/Engine/Rendering/ConsoleRenderingEngine.cs new file mode 100644 index 0000000..75235dc --- /dev/null +++ b/src/TUI/Engine/Rendering/ConsoleRenderingEngine.cs @@ -0,0 +1,129 @@ +using System.Diagnostics; +using TUI.Engine.Nodes; +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Nodes.Containers; + +namespace TUI.Engine.Rendering; + +public class ConsoleRenderingEngine : IRenderingEngine +{ + private readonly IWindow _window; + + public ConsoleRenderingEngine(IWindow window) + { + _window = window; + } + + public void Render(IContainer container, Size? defaultSize = null) + { + defaultSize ??= new Size(_window.Width, _window.Height); + + var defaultChildrenSize = new Size( + Width: container.Orientation == Orientation.Horizontal + ? defaultSize.Width / container.Nodes.Count + : defaultSize.Width, + Height: container.Orientation == Orientation.Vertical + ? defaultSize.Height / container.Nodes.Count + : defaultSize.Height + ); + + var controlNumber = 0; + var nodePosition = new NodePosition(Left: 0, Top: 0); + + Debugger.Log(0, "Render", $"{nodePosition} {defaultSize} {container.GetType().Name}\n"); + Helper.ShowBackground(nodePosition, defaultSize); + + while (controlNumber < container.Nodes.Count) + { + var node = container.Nodes[controlNumber]; + + nodePosition = RenderNode(node, container.Orientation, defaultChildrenSize, nodePosition); + controlNumber++; + } + } + + + private NodePosition RenderNode(INode node, Orientation orientation, Size defaultSize, NodePosition position) + { + switch (node) + { + case IContainer container when orientation == Orientation.Horizontal: + Render(container, defaultSize); + return position with + { + Left = position.Left + defaultSize.Width + }; + case IContainer container when orientation == Orientation.Vertical: + Render(container, defaultSize); + return position with + { + Top = position.Top + defaultSize.Height + }; + case IComponent component when orientation == Orientation.Horizontal: + var componentWidth = RenderComponent(component, position, defaultSize).Width; + return position with + { + Left = position.Left + (defaultSize.Width <= componentWidth ? componentWidth : defaultSize.Width) + }; + case IComponent component when orientation == Orientation.Vertical: + var componentHeight = RenderComponent(component, position, defaultSize).Height; + return position with + { + Top = position.Top + (defaultSize.Height <= componentHeight ? componentHeight : defaultSize.Height) + }; + default: + throw new InvalidCastException(); + } + } + + private Size RenderComponent(IComponent component, NodePosition defaultPosition, Size defaultSize) + { + var content = component.Render(); + + var maxWidth = _window.Width - defaultPosition.Left; + var maxHeight = _window.Height - defaultPosition.Top; + + var leftPosition = defaultPosition.Left + (int)(component.Padding?.Left ?? 0) + + CompensationLeft(component.Alignment.Horizontal, defaultSize, content.GetSize()); + var topPosition = defaultPosition.Top + (int)(component.Padding?.Top ?? 0) + + CompensationTop(component.Alignment.Vertical, defaultSize, content.GetSize()); + + + Debugger.Log(0, "Render", $"{component.GetType().Name} with position [{leftPosition},{topPosition}]\n"); + + var rows = content.Rows(maxWidth, maxHeight); + + Helper.ShowBackground(defaultPosition, defaultSize); + + foreach (var row in rows) + { + _window.SetCursorPosition(leftPosition, topPosition); + _window.Write(row); + + topPosition++; + } + + return content.GetSize(); + } + + private static int CompensationLeft(Horizontal componentHorizontal, Size defaultSize, Size realSize) => + componentHorizontal switch + { + Horizontal.Left => 0, + Horizontal.Center => (defaultSize.Width - realSize.Width) / 2, + Horizontal.Right => defaultSize.Width - realSize.Width, + _ => 0 + }; + + private static int CompensationTop(Vertical componentVertical, Size defaultSize, Size realSize) + => + componentVertical switch + { + Vertical.Top => 0, + Vertical.Center => (defaultSize.Height - realSize.Height) / 2, + Vertical.Bottom => defaultSize.Height - realSize.Height, + _ => 0 + }; +} \ No newline at end of file diff --git a/src/TUI/Engine/Rendering/ConsoleWindow.cs b/src/TUI/Engine/Rendering/ConsoleWindow.cs new file mode 100644 index 0000000..20908ae --- /dev/null +++ b/src/TUI/Engine/Rendering/ConsoleWindow.cs @@ -0,0 +1,9 @@ +namespace TUI.Engine.Rendering; + +public class ConsoleWindow : IWindow +{ + public int Width => Console.WindowWidth; + public int Height => Console.WindowHeight; + public void SetCursorPosition(int left, int top) => Console.SetCursorPosition(left, top); + public void Write(string value) => Console.Write(value); +} \ No newline at end of file diff --git a/src/TUI/Engine/Rendering/IRenderingEngine.cs b/src/TUI/Engine/Rendering/IRenderingEngine.cs new file mode 100644 index 0000000..9cc91bd --- /dev/null +++ b/src/TUI/Engine/Rendering/IRenderingEngine.cs @@ -0,0 +1,9 @@ +using TUI.Engine.Nodes.Attributes; +using TUI.Engine.Nodes.Containers; + +namespace TUI.Engine.Rendering; + +public interface IRenderingEngine +{ + void Render(IContainer container, Size? defaultSize); +} \ No newline at end of file diff --git a/src/TUI/Engine/Rendering/IWindow.cs b/src/TUI/Engine/Rendering/IWindow.cs new file mode 100644 index 0000000..5fd7dc6 --- /dev/null +++ b/src/TUI/Engine/Rendering/IWindow.cs @@ -0,0 +1,9 @@ +namespace TUI.Engine.Rendering; + +public interface IWindow +{ + int Width { get; } + int Height { get; } + void SetCursorPosition(int left, int top); + void Write(string value); +} \ No newline at end of file diff --git a/src/TUI/Engine/Symbols.cs b/src/TUI/Engine/Symbols.cs new file mode 100644 index 0000000..ea35302 --- /dev/null +++ b/src/TUI/Engine/Symbols.cs @@ -0,0 +1,25 @@ +namespace TUI.Engine; + +public static class Symbols +{ + public const char Space = ' '; + public const char Copyright = '©'; + public const char GitLab = ''; + public const char GitHub = ''; + public const char Git = ''; + public const char LineBreak = '\n'; + + public static class Lines + { + public const char Vertical = '│'; + public const char Horizontal = '─'; + } + + public static class Angles + { + public const char RightTop = '┐'; + public const char LeftBottom = '└'; + public const char LeftTop = '┌'; + public const char RightBottom = '┘'; + } +} diff --git a/src/TUI/Engine/Theme/Indentation.cs b/src/TUI/Engine/Theme/Indentation.cs new file mode 100644 index 0000000..eb9d7d9 --- /dev/null +++ b/src/TUI/Engine/Theme/Indentation.cs @@ -0,0 +1,7 @@ +namespace TUI.Engine.Theme; + +public static class Indentation +{ + public const Level Default = Level.Normal; + public const Level BorderWidth = Level.Normal; +} \ No newline at end of file diff --git a/src/TUI/Engine/Theme/Level.cs b/src/TUI/Engine/Theme/Level.cs new file mode 100644 index 0000000..8c246fa --- /dev/null +++ b/src/TUI/Engine/Theme/Level.cs @@ -0,0 +1,7 @@ +namespace TUI.Engine.Theme; + +public enum Level +{ + None = 0, + Normal = 1 +} \ No newline at end of file diff --git a/src/TUI/Engine/Theme/Palette.cs b/src/TUI/Engine/Theme/Palette.cs new file mode 100644 index 0000000..4b700d0 --- /dev/null +++ b/src/TUI/Engine/Theme/Palette.cs @@ -0,0 +1,28 @@ +using Pastel; + +namespace TUI.Engine.Theme; + +public static class Palette +{ + private const string HoverColor = "292928"; + private const string PrimaryColor = "84BA64"; + private const string HintColor = "71797E"; + private const string ErrorColor = "CA3433"; + private const string WarningColor = "EC9706"; + private const string InfoColor = "25799F"; + + public static string Main(this string currentText, bool isFocused = true) => + isFocused + ? currentText.Pastel(PrimaryColor) + : Hint(currentText); + + public static string Hint(this string currentText) => currentText.Pastel(HintColor); + + public static string Disable(this string currentText) => currentText.RemoveColors().Pastel(HintColor); + + public static string Warning(this string currentText) => currentText.Pastel(WarningColor); + + public static string Error(this string currentText) => currentText.Pastel(ErrorColor); + + public static string Info(this string currentText) => currentText.Pastel(InfoColor); +} \ No newline at end of file diff --git a/src/TUI/Extensions.cs b/src/TUI/Extensions.cs index 4acb68f..63945e3 100644 --- a/src/TUI/Extensions.cs +++ b/src/TUI/Extensions.cs @@ -13,7 +13,7 @@ public static class Extensions public static string Repeat(this char symbol, int repeatCount) { - return repeatCount < 0 ? "" : new string(symbol, repeatCount); + return repeatCount < 0 ? string.Empty : new string(symbol, repeatCount); } public static string RemoveColors(this string text) @@ -30,7 +30,7 @@ public static class Extensions return stringInfo.LengthInTextElements; } - public static Version? ToVersion(this string textVersion) + public static Version ToVersion(this string textVersion) { var version = textVersion.Replace("^", "").Replace("~", "").Split("."); var major = Convert.ToInt32(version[0]); diff --git a/src/TUI/Pages/DependenciesPage.cs b/src/TUI/Pages/DependenciesPage.cs new file mode 100644 index 0000000..b535af6 --- /dev/null +++ b/src/TUI/Pages/DependenciesPage.cs @@ -0,0 +1,103 @@ +using System.Diagnostics; +using TUI.Components.Controls; +using TUI.Components.Layouts; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Rendering; +using TUI.Engine.Theme; + +namespace TUI.Pages; + +public class DependenciesPage +{ + public void Open() + { + Debugger.Log(0, "Event", "Open page dependencies\n"); + + var window = new ConsoleWindow(); + var renderEngine = new ConsoleRenderingEngine(window); + var header = new HeaderContainer(); + var copyright = new Copyright() + .Set(right: Level.Normal) + .Set(Horizontal.Right, Vertical.Bottom); + + var layout = new DashboardLayout().AddHeader(header).AddFooter(copyright); + // CommandLine = new CommandLine(); + // DependenciesView = new DependenciesView(); + renderEngine.Render(layout); + } + + // private bool _commandLineInDisplay; + + // private ProjectDto _currentProjectDto; + + // private bool _headerInDisplay = true; + + + // public string FocusedElement { get; set; } = ""; + + // public void OpenDeps(ProjectDto projectDto) + // { + // _currentProjectDto = projectDto; + // var dashboardPosition = new ControlPosition(0, Header.Height); + // // DependencyDashboard.IsFocused = true; + // // CommandLine.IsFocused = false; + // DependenciesView.Render(_currentProjectDto, dashboardPosition); + // } + // + // private void ResizeDependencies() + // { + // var topPosition = 0; + // topPosition += _commandLineInDisplay ? CommandLine.Height : 0; + // topPosition += _headerInDisplay ? Header.Height : 0; + // var dashboardPosition = new ControlPosition(0, topPosition); + // DependenciesView.Render(_currentProjectDto, dashboardPosition); + // } + // + // public void Render() + // { + // var headerPosition = new ControlPosition(0, 0); + // Header.Render(headerPosition); + // + // const string copyrightText = "Kolosov Aleksandr"; + // var copyrightPosition = new ControlPosition( + // Console.WindowWidth - copyrightText.Width(), + // Console.WindowHeight); + // CopyrightControl.Render(copyrightText, copyrightPosition); + // } + // + // public void ToggleHeader() + // { + // _headerInDisplay = !_headerInDisplay; + // if (_headerInDisplay) + // { + // var headerPosition = new ControlPosition(0, 0); + // Header.Render(headerPosition); + // } + // + // ResizeDependencies(); + // } + // + // public void Next() + // { + // DependenciesView.Next(); + // } + // + // public void Previous() + // { + // DependenciesView.Previous(); + // } + // + // public void OpenCommandLine() + // { + // var commandLinePosition = new ControlPosition(0, _headerInDisplay ? Header.Height : 0); + // // CommandLine.IsFocused = true; + // // DependencyDashboard.IsFocused = false; + // FocusedElement = nameof(CommandLine); + // CommandLine.Render(commandLinePosition); + // _commandLineInDisplay = true; + // ResizeDependencies(); + // Console.SetCursorPosition(commandLinePosition.Left + Theme.Padding + Theme.BorderWidth + 2, + // commandLinePosition.Top + Theme.BorderWidth); + // Console.CursorVisible = true; + // } +} \ No newline at end of file diff --git a/src/TUI/Program.cs b/src/TUI/Program.cs index 5ea9cc9..775f06f 100644 --- a/src/TUI/Program.cs +++ b/src/TUI/Program.cs @@ -1,60 +1,61 @@ -using TUI.Controls; -using TUI.Domain; +using TUI.Pages; Console.Clear(); Console.CursorVisible = false; -var settings = Settings.Init(); +// var settings = Settings.Init(); -var display = new Display(); -display.OpenDeps(settings.Projects[0]); +var dependenciesPage = new DependenciesPage(); +dependenciesPage.Open(); + +// display.OpenDeps(settings.Projects[0]); var key = new ConsoleKeyInfo('1', ConsoleKey.NoName, false, false, false); var waitCommand = true; do { - if (key.Key == ConsoleKey.Q && !display.CommandLine.IsFocused) - { - waitCommand = false; - continue; - } - - if (display.CommandLine.IsFocused) - { - switch (key.Key) - { - case ConsoleKey.Escape: - display.CommandLine.IsFocused = false; - break; - default: - Console.Write(key.KeyChar); - - break; - } - } - else - { - switch (key.KeyChar) - { - case ':': - display.OpenCommandLine(); - break; - } - - switch (key.Key) - { - case ConsoleKey.DownArrow: - display.Next(); - break; - case ConsoleKey.UpArrow: - display.Previous(); - break; - case ConsoleKey.E: - display.ToggleHeader(); - break; - } - } + // if (key.Key == ConsoleKey.Q && !display.CommandLine.IsFocused) + // { + // waitCommand = false; + // continue; + // } + // + // if (display.CommandLine.IsFocused) + // { + // switch (key.Key) + // { + // case ConsoleKey.Escape: + // display.CommandLine.IsFocused = false; + // break; + // default: + // Console.Write(key.KeyChar); + // + // break; + // } + // } + // else + // { + // switch (key.KeyChar) + // { + // case ':': + // display.OpenCommandLine(); + // break; + // } + // + // switch (key.Key) + // { + // case ConsoleKey.DownArrow: + // display.Next(); + // break; + // case ConsoleKey.UpArrow: + // display.Previous(); + // break; + // case ConsoleKey.E: + // display.Toggle(); + // break; + // } + // } key = Console.ReadKey(true); } while (waitCommand); diff --git a/src/TUI/Store/DependenciesStore.cs b/src/TUI/Store/DependenciesStore.cs new file mode 100644 index 0000000..141484f --- /dev/null +++ b/src/TUI/Store/DependenciesStore.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using TUI.Domain; +using TUI.Settings; + +namespace TUI.Store; + +public static class DependenciesStore +{ + private readonly static Dictionary Packages = new(); + + private static Package DownloadPackage(SourceDto sourceDto) + { + var endpoint = sourceDto.Tags.Have("gitlab") ? GetGitlabEndpoint(sourceDto) : sourceDto.Repo; + if (Packages.TryGetValue(endpoint, out var downloadPackage)) return downloadPackage; + + using HttpClient client = new(); + var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult(); + var package = JsonSerializer.Deserialize(json); + Packages.Add(endpoint, package); + return package; + } + + private static string GetGitlabEndpoint(SourceDto sourceDto) + { + var token = Environment.GetEnvironmentVariable("TLD_GITLAB_PAT"); + return $"{sourceDto.Repo}/api/v4/projects/{sourceDto.ProjectId}/repository/files/package.json/raw?" + + $"private_token={token}&ref=dev"; + } +} \ No newline at end of file diff --git a/src/TUI/UserInterface/CommandLine.cs b/src/TUI/UserInterface/CommandLine.cs index 20969fb..a4cd92a 100644 --- a/src/TUI/UserInterface/CommandLine.cs +++ b/src/TUI/UserInterface/CommandLine.cs @@ -1,17 +1,19 @@ -using TUI.Controls; - - -namespace TUI.UserInterface; - -public class CommandLine : Dashboard -{ - public const int Height = 3; - - public void Render(Position position) - { - base.Render("Command", position, Height); - - Console.SetCursorPosition(position.Left + Theme.BorderWidth + Theme.Padding, position.Top + Theme.BorderWidth); - Console.Write(">"); - } -} \ No newline at end of file +// using TUI.Components.Controls; +// using TUI.Engine; +// using TUI.Engine.Controls; +// +// +// namespace TUI.UserInterface; +// // +// // public class CommandLine : DashboardControl +// // { +// // public const int Height = 3; +// // +// // public void Render(ControlPosition position) +// // { +// // base.Render("Command", position, Height); +// // +// // Console.SetCursorPosition(position.Left + Theme.BorderWidth + Theme.Padding, position.Top + Theme.BorderWidth); +// // Console.Write(">"); +// // } +// // } \ No newline at end of file diff --git a/src/TUI/UserInterface/Header.cs b/src/TUI/UserInterface/Header.cs deleted file mode 100644 index bda1a3f..0000000 --- a/src/TUI/UserInterface/Header.cs +++ /dev/null @@ -1,79 +0,0 @@ -using TUI.Controls; - - -namespace TUI.UserInterface; - -public class Header : IControl -{ - public const int LogoWidth = 16; - public const int Height = 6; - public const int MaxHeaderBlocksWidth = 16; - - private readonly Dictionary _hints = new() - { - { "󰎔", "too new".Info() }, - { "", "so good" }, - { "", "be nice".Primary() }, - { "󰬟", "too old".Warning() } - }; - - - private readonly Dictionary _hotKeys = new() - { - { "", "select prev" }, - { "", "select next" }, - { "󰬌", "toggle head" }, - { "󰬘", "quit" } - }; - - private readonly Dictionary _tags = new() - { - { Icons.Auth, "Auth" }, - { Icons.NetworkPublic, "WWW" }, - { Icons.SEO, "SEO" }, - { Icons.GitLab, "VCS" } - }; - - public void Render(Position position) - { - Console.SetCursorPosition(position.Left, position.Top); - - for (var i = 1; i <= Height; i++) Console.WriteLine(new string(' ', Console.WindowWidth - LogoWidth)); - - RenderBlock(0, _hints); - RenderBlock(1, _tags); - RenderBlock(2, Icons.Applications); - RenderBlock(3, _hotKeys); - RenderLogo(); - } - - private static void RenderBlock(int blockNumber, Dictionary items) - { - var leftPadding = Theme.Padding + blockNumber * MaxHeaderBlocksWidth; - - var hotKeyNumber = 0; - foreach (var item in items) - { - Console.SetCursorPosition(leftPadding, Theme.Padding + hotKeyNumber++); - Console.Write((item.Key + " " + item.Value).Hint()); - } - } - - - private static void RenderLogo() - { - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 0); - Console.WriteLine(" ╭━━━━┳╮".Primary() + "╱╱".Hint() + "╭━━━╮ ".Primary()); - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 1); - Console.WriteLine(" ┃╭╮╭╮┃┃".Primary() + "╱╱".Hint() + "╰╮╭╮┃ ".Primary()); - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 2); - Console.WriteLine(" ╰╯┃┃╰┫┃".Primary() + "╱╱╱".Hint() + "┃┃┃┃ ".Primary()); - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 3); - Console.WriteLine(" ╱╱".Hint() + "┃┃".Primary() + "╱".Hint() + "┃┃".Primary() + "╱".Hint() + - "╭╮┃┃┃┃ ".Primary()); - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 4); - Console.WriteLine(" ╱╱╱".Hint() + "┃┃".Primary() + "╱".Hint() + "┃╰━╯┣╯╰╯┃ ".Primary()); - Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 5); - Console.WriteLine("╱╱╱╱".Hint() + "╰╯".Primary() + "╱".Hint() + "╰━━━┻━━━╯ ".Primary()); - } -} \ No newline at end of file diff --git a/src/TUI/UserInterface/Icons.cs b/src/TUI/UserInterface/Icons.cs index fb6976c..b86cc29 100644 --- a/src/TUI/UserInterface/Icons.cs +++ b/src/TUI/UserInterface/Icons.cs @@ -1,4 +1,5 @@ using Pastel; +using TUI.Engine.Theme; namespace TUI.UserInterface; @@ -13,22 +14,22 @@ public static class Icons { Api, "api" } }; - public static string GitLab => GetIcon("", "E24329"); - public static string GitHub => GetIcon("", "ADBAC7"); - public static string Git => GetIcon("", "F14E32"); + public static string GitLab => GetIcon("", "E24329"); + public static string GitHub => GetIcon("", "ADBAC7"); + public static string Git => GetIcon("", "F14E32"); public static string NetworkPublic => GetIcon("󰞉", "00FFFF"); public static string NetworkPrivate => GetIcon("󰕑"); public static string Undefined => GetIcon(""); - public static string Site => GetIcon("", "BF40BF"); - public static string Api => GetIcon("", "7F52FF"); + public static string Site => GetIcon("", "BF40BF"); + public static string Api => GetIcon("", "7F52FF"); public static string DockerImage => GetIcon("󰡨", "086DD7"); - public static string NpmPackage => GetIcon("", "CB0000"); - public static string SEO => GetIcon("󰚩", "4285F4"); - public static string Auth => GetIcon("", "FFD700"); + public static string NpmPackage => GetIcon("", "CB0000"); + public static string SEO => GetIcon("󰚩", "4285F4"); + public static string Auth => GetIcon("", "FFD700"); public static string NotFound => GetIcon(""); private static string GetIcon(string icon, string? activeColor = null) { - return icon.Pastel(activeColor ?? Palette.HintColor); + return activeColor != null ? icon.Pastel(activeColor) : icon.Hint(); } } \ No newline at end of file diff --git a/src/TUI/UserInterface/Palette.cs b/src/TUI/UserInterface/Palette.cs deleted file mode 100644 index 8cc3f6f..0000000 --- a/src/TUI/UserInterface/Palette.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Pastel; - - -namespace TUI.UserInterface; - -public static class Palette -{ - public const string HoverColor = "292928"; - public const string PrimaryColor = "84BA64"; - public const string HintColor = "71797E"; - public const string ErrorColor = "CA3433"; - public const string WarningColor = "EC9706"; - public const string InfoColor = "25799F"; - - public static string Primary(this string currentText, bool isFocused = true) - { - return isFocused - ? currentText.Pastel(PrimaryColor) - : Hint(currentText); - } - - public static string Hint(this string currentText) - { - return currentText.Pastel(HintColor); - } - - public static string Disable(this string currentText) - { - return currentText.RemoveColors().Pastel(HintColor); - } - - public static string Warning(this string currentText) - { - return currentText.Pastel(WarningColor); - } - - public static string Error(this string currentText) - { - return currentText.Pastel(ErrorColor); - } - - public static string Info(this string currentText) - { - return currentText.Pastel(InfoColor); - } -} \ No newline at end of file diff --git a/src/TUI/UserInterface/Panel.cs b/src/TUI/UserInterface/Panel.cs index 9bba507..157062e 100644 --- a/src/TUI/UserInterface/Panel.cs +++ b/src/TUI/UserInterface/Panel.cs @@ -1,3 +1,4 @@ +using TUI.Engine.Theme; using TUI.Settings; @@ -18,9 +19,9 @@ public static class Panel { for (var index = 0; index < sources.Length; index++) { - Console.SetCursorPosition(Theme.Padding, - 6 + index + _marginTop + BorderWidth + - Theme.Padding); + Console.SetCursorPosition(Convert.ToInt32(Indentation.Default), + 6 + index + _marginTop + BorderWidth + + Convert.ToInt32(Indentation.Default)); if (selectedRowNumber == index + 1) { @@ -32,7 +33,7 @@ public static class Panel for (var index = 0; index < sources.Length; index++) Console.SetCursorPosition(TitleWidth, - 6 + index + _marginTop + BorderWidth + Theme.Padding); + 6 + index + _marginTop + BorderWidth + Convert.ToInt32(Indentation.Default)); // var source = sources[index]; // var package = DownloadPackage(source); // var resultText = package.Dependencies.React; diff --git a/src/TUI/UserInterface/Theme.cs b/src/TUI/UserInterface/Theme.cs deleted file mode 100644 index dec24e8..0000000 --- a/src/TUI/UserInterface/Theme.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TUI.UserInterface; - -public static class Theme -{ - public static int Padding = 1; - public static int BorderWidth = 1; -} \ No newline at end of file diff --git a/tests/WIdgets/TUI.Tests/ComponentBaseTests.cs b/tests/WIdgets/TUI.Tests/ComponentBaseTests.cs new file mode 100644 index 0000000..77bb47a --- /dev/null +++ b/tests/WIdgets/TUI.Tests/ComponentBaseTests.cs @@ -0,0 +1,63 @@ +using FluentAssertions; +using TUI.Components.Controls.Statics; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Theme; + +namespace Widgets.Tests; + +public class ComponentBaseTests +{ + [Fact] + public void WhenUseChainingSaveAllChange() + { + var component = new Logo() + .Set(Level.Normal) + .Set(vertical: Vertical.Center, horizontal: Horizontal.Center); + + component.Padding.Top.Should().Be(Level.Normal); + component.Padding.Left.Should().Be(Level.Normal); + component.Padding.Bottom.Should().Be(Level.Normal); + component.Padding.Right.Should().Be(Level.Normal); + component.Alignment.Horizontal.Should().Be(Horizontal.Center); + component.Alignment.Vertical.Should().Be(Vertical.Center); + } + + [Fact] + public void WhenSetPaddingsSaveAllChange() + { + var component = new Logo(); + + component.Set(Level.Normal); + + component.Padding.Top.Should().Be(Level.Normal); + component.Padding.Left.Should().Be(Level.Normal); + component.Padding.Bottom.Should().Be(Level.Normal); + component.Padding.Right.Should().Be(Level.Normal); + } + + [Theory] + [InlineData(Vertical.Bottom)] + [InlineData(Vertical.Center)] + [InlineData(Vertical.Top)] + public void WhenSetVerticalAlignSaveAllChange(Vertical alignment) + { + var component = new Logo(); + + component.Set(vertical: alignment); + + component.Alignment.Vertical.Should().Be(alignment); + } + + [Theory] + [InlineData(Horizontal.Left)] + [InlineData(Horizontal.Center)] + [InlineData(Horizontal.Right)] + public void WhenSetHorizontalAlignSaveAllChange(Horizontal alignment) + { + var component = new Logo(); + + component.Set(horizontal: alignment); + + component.Alignment.Horizontal.Should().Be(alignment); + } +} \ No newline at end of file diff --git a/tests/WIdgets/TUI.Tests/ConsoleRenderingEngineTests.cs b/tests/WIdgets/TUI.Tests/ConsoleRenderingEngineTests.cs new file mode 100644 index 0000000..08ad62a --- /dev/null +++ b/tests/WIdgets/TUI.Tests/ConsoleRenderingEngineTests.cs @@ -0,0 +1,184 @@ +using Moq; +using TUI.Engine.Nodes; +using TUI.Engine.Nodes.Attributes.Alignments; +using TUI.Engine.Nodes.Components; +using TUI.Engine.Nodes.Containers; +using TUI.Engine.Rendering; + +namespace Widgets.Tests; + +public class ConsoleRenderingEngineTests +{ + private readonly IComponent _component; + + public ConsoleRenderingEngineTests() + { + _component = Mock.Of(c => + c.Render() == new Content("Lorem") && + c.Alignment == new Alignment(Horizontal.Left, Vertical.Top)); + } + + [Fact] + public void RenderSimple() + { + var window = Mock.Of(w => w.Width == 9 && w.Height == 1); + var nodes = new Nodes { _component }; + var root = Mock.Of(r => r.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Once()); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Once()); + } + + [Theory] + [InlineData(Horizontal.Left, "Lorem", 10, 0)] + [InlineData(Horizontal.Center, "Lorem", 10, 2)] + [InlineData(Horizontal.Center, "Lo", 10, 4)] + [InlineData(Horizontal.Center, "Lorem", 9, 2)] + [InlineData(Horizontal.Center, "Lorem", 11, 3)] + [InlineData(Horizontal.Right, "Lorem", 10, 5)] + [InlineData(Horizontal.Right, "Lo", 10, 8)] + public void RenderWithHorizontalAlignment(Horizontal alignment, string content, int windowSize, + int expectedPosition) + { + var window = Mock.Of(w => w.Width == windowSize && w.Height == windowSize); + var component = Mock.Of(c => c.Render() == new Content(content) && + c.Alignment == new Alignment(alignment, Vertical.Top)); + var nodes = new Nodes { component }; + var root = Mock.Of(r => r.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.Write(content), Times.Once()); + Mock.Get(window).Verify(w => w.SetCursorPosition(expectedPosition, 0), Times.Once()); + } + + [Theory] + [InlineData(Vertical.Top, "v", 5, new[] { 0 })] + [InlineData(Vertical.Top, "v\nv", 5, new[] { 0, 1 })] + [InlineData(Vertical.Top, "v\nv\nv", 5, new[] { 0, 1, 2 })] + [InlineData(Vertical.Center, "v", 1, new[] { 0 })] + [InlineData(Vertical.Center, "v", 4, new[] { 1 })] + [InlineData(Vertical.Center, "v", 5, new[] { 2 })] + [InlineData(Vertical.Center, "v", 6, new[] { 2 })] + [InlineData(Vertical.Center, "v\nv", 4, new[] { 1, 2 })] + [InlineData(Vertical.Center, "v\nv", 5, new[] { 1, 2 })] + [InlineData(Vertical.Center, "v\nv", 6, new[] { 2, 3 })] + [InlineData(Vertical.Bottom, "v", 5, new[] { 4 })] + [InlineData(Vertical.Bottom, "v\nv", 2, new[] { 0, 1 })] + [InlineData(Vertical.Bottom, "v\nv", 3, new[] { 1, 2 })] + [InlineData(Vertical.Bottom, "v\nv\nv\nv", 5, new[] { 1, 2, 3, 4 })] + public void RenderWithVerticalAlignment(Vertical alignment, string content, int windowSize, int[] expectedPositions) + { + var window = Mock.Of(w => w.Width == windowSize && w.Height == windowSize); + var component = Mock.Of(c => c.Render() == new Content(content) && + c.Alignment == new Alignment(Horizontal.Left, alignment)); + var nodes = new Nodes { component }; + var root = Mock.Of(r => r.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(root); + + foreach (var expectedCursorPosition in expectedPositions) + { + Mock.Get(window).Verify(w => w.SetCursorPosition(0, expectedCursorPosition), Times.Once()); + } + } + + [Theory] + [InlineData(Horizontal.Left, Vertical.Top, 0, 0)] + [InlineData(Horizontal.Left, Vertical.Center, 0, 2)] + [InlineData(Horizontal.Left, Vertical.Bottom, 0, 4)] + [InlineData(Horizontal.Center, Vertical.Top, 2, 0)] + [InlineData(Horizontal.Center, Vertical.Center, 2, 2)] + [InlineData(Horizontal.Center, Vertical.Bottom, 2, 4)] + [InlineData(Horizontal.Right, Vertical.Top, 4, 0)] + [InlineData(Horizontal.Right, Vertical.Center, 4, 2)] + [InlineData(Horizontal.Right, Vertical.Bottom, 4, 4)] + public void RenderWithAlignment(Horizontal horizontal, Vertical vertical, int expectedLeft, int expectedTop) + { + var window = Mock.Of(w => w.Width == 6 && w.Height == 5); + var component = Mock.Of(c => c.Render() == new Content("VV") && + c.Alignment == new Alignment(horizontal, vertical)); + var nodes = new Nodes { component }; + var root = Mock.Of(r => r.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(expectedLeft, expectedTop), Times.Once()); + } + + [Theory] + [InlineData(Orientation.Horizontal, 9, 1)] + [InlineData(Orientation.Vertical, 5, 1)] + public void RenderWithOverload(Orientation orientation, int rootWidth, int rootHeight) + { + var window = Mock.Of(w => w.Width == rootWidth && w.Height == rootHeight); + var nodes = new Nodes { _component, _component }; + var root = Mock.Of(r => r.Nodes == nodes && r.Orientation == orientation); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Once()); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Once()); + } + + [Fact] + public void RenderVerticalWithDoubleComponent() + { + var window = Mock.Of(w => w.Height == 2 && w.Width == 10); + var nodes = new Nodes { _component, _component }; + var root = Mock.Of(r => r.Nodes == nodes && r.Orientation == Orientation.Vertical); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Once()); + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 1), Times.Once()); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Exactly(2)); + } + + [Fact] + public void RenderHorizontalWithDoubleComponent() + { + var window = Mock.Of(w => w.Width == 10 && w.Height == 1); + var nodes = new Nodes { _component, _component }; + var container = Mock.Of(g => g.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(container); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.SetCursorPosition(5, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Exactly(2)); + } + + [Fact] + public void RenderWithMultipleComponent() + { + var window = Mock.Of(w => w.Width == 24 && w.Height == 1); + var nodes = new Nodes { _component, _component, _component, _component }; + var root = Mock.Of(r => r.Nodes == nodes); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.SetCursorPosition(6, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.SetCursorPosition(12, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.SetCursorPosition(18, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Exactly(4)); + } + + [Fact] + public void RenderWithContainerAndComponent() + { + var window = Mock.Of(w => w.Width == 10 && w.Height == 2); + var container = Mock.Of(c => c.Nodes == new Nodes { _component }); + var nodes = new Nodes { container, _component }; + var root = Mock.Of(r => r.Nodes == nodes && r.Orientation == Orientation.Vertical); + + new ConsoleRenderingEngine(window).Render(root); + + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 0), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.SetCursorPosition(0, 1), Times.Exactly(1)); + Mock.Get(window).Verify(w => w.Write("Lorem"), Times.Exactly(2)); + } +} \ No newline at end of file diff --git a/tests/WIdgets/TUI.Tests/Controls/LogoTests.cs b/tests/WIdgets/TUI.Tests/Controls/LogoTests.cs new file mode 100644 index 0000000..cc3273c --- /dev/null +++ b/tests/WIdgets/TUI.Tests/Controls/LogoTests.cs @@ -0,0 +1,18 @@ +using TUI.Components.Controls.Statics; + +namespace Widgets.Tests.Controls; + +public class LogoTests +{ + [Fact] + public void Simple() + { + var logo = new Logo(); + + var render = logo.Render().ToString(); + + Assert.Equal( + " \u001b[38;2;132;186;100m\u256d\u2501\u2501\u2501\u2501\u2533\u256e\u001b[0m\u001b[38;2;113;121;126m\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u256d\u2501\u2501\u2501\u256e\u001b[0m\n \u001b[38;2;132;186;100m\u2503\u256d\u256e\u256d\u256e\u2503\u2503\u001b[0m\u001b[38;2;113;121;126m\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u2570\u256e\u256d\u256e\u2503\u001b[0m\n \u001b[38;2;132;186;100m\u2570\u256f\u2503\u2503\u2570\u252b\u2503\u001b[0m\u001b[38;2;113;121;126m\u2571\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u2503\u2503\u2503\u2503\u001b[0m\n \u001b[38;2;113;121;126m\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u2503\u2503\u001b[0m\u001b[38;2;113;121;126m\u2571\u001b[0m\u001b[38;2;132;186;100m\u2503\u2503\u001b[0m\u001b[38;2;113;121;126m\u2571\u001b[0m\u001b[38;2;132;186;100m\u256d\u256e\u2503\u2503\u2503\u2503\u001b[0m\n \u001b[38;2;113;121;126m\u2571\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u2503\u2503\u001b[0m\u001b[38;2;113;121;126m\u2571\u001b[0m\u001b[38;2;132;186;100m\u2503\u2570\u2501\u256f\u2523\u256f\u2570\u256f\u2503\u001b[0m\n\u001b[38;2;113;121;126m\u2571\u2571\u2571\u2571\u001b[0m\u001b[38;2;132;186;100m\u2570\u256f\u001b[0m\u001b[38;2;113;121;126m\u2571\u001b[0m\u001b[38;2;132;186;100m\u2570\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u256f\u001b[0m\n", + render); + } +} \ No newline at end of file diff --git a/tests/WIdgets/TUI.Tests/TUI.Tests.csproj b/tests/WIdgets/TUI.Tests/TUI.Tests.csproj new file mode 100644 index 0000000..5f8628f --- /dev/null +++ b/tests/WIdgets/TUI.Tests/TUI.Tests.csproj @@ -0,0 +1,32 @@ + + + + net7.0 + enable + enable + + false + true + Widgets.Tests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/WIdgets/TUI.Tests/Usings.cs b/tests/WIdgets/TUI.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/WIdgets/TUI.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file