diff --git a/TLD.sln b/TLD.sln index 245bd53..aa4e2cc 100644 --- a/TLD.sln +++ b/TLD.sln @@ -2,6 +2,12 @@ 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.Engine.Tests", "tests\TUI.Engine.Tests\TUI.Engine.Tests.csproj", "{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Engine", "src\TUI.Engine\TUI.Engine.csproj", "{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Controls.Tests", "tests\TUI.Controls.Tests\TUI.Controls.Tests.csproj", "{59A0E843-8664-45B9-AD0C-52B8FE38986A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -16,5 +22,17 @@ 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 + {38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Release|Any CPU.Build.0 = Release|Any CPU + {59A0E843-8664-45B9-AD0C-52B8FE38986A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59A0E843-8664-45B9-AD0C-52B8FE38986A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59A0E843-8664-45B9-AD0C-52B8FE38986A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59A0E843-8664-45B9-AD0C-52B8FE38986A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/docs/Features.txt b/docs/Features.txt new file mode 100644 index 0000000..49b2b06 --- /dev/null +++ b/docs/Features.txt @@ -0,0 +1,76 @@ + + 󰘳 1 󱓟 releases  select prev + 󰘳 2 󰧑 competencies  select next + 󰘳 3  dependencies 󰬌 toggle head + 󰘳 4 󰬘 quit + + 󰎔 too new  Auth  package +  so good 󰞉 WWW 󰡨 image +  be nice 󰚩 SEO  site + 󰬟 too old  VCS  api + +   󱓟 releases  Fact  2023 +   󱓟 releases  Fact +   󱓟 releases  Planned +   󱓟 releases  Planned  2024-12-31 xfirm +   󰧑 competencies +   󰶼 growth zone +    dependencies +   󱁤 tools + + + +󰦖 wait 󰲽 build +󱔢 reopen 󱞈 testing +󱞇 in progress 󰦕 done +󱞇 review 󱄊 cancel + +┌──────────────────────────────────────────────────────────────────────── Planned release ─────────────────────────────────────────────────────────────────┐ +│ 󱓟 Planned 󱜜 󰲌 application  Tasks +│ 2024-12-31 1d elk-site 4/11 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖 +│ elk-site 2/5 󰦕 󰦕 󰦖 󰦖 󰦖 +│ elk-site 22/22 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖 +│ 2024-12-31 12d xfirm 4/12 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖 +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + + +󰨑 show stopper 󰦖 wait 󰲽 build  feature +󰨑 first of all 󱔢 reopen 󱞈 testing  bug +󰨑 just do it 󱞇 in progress 󰦕 done +󰨑 doesn't rush 󱞇 review 󱄊 cancel + +┌──────────────────────────────────────────────────────────────────────── checkperson-site ────────────────────────────────────────────────────────────────┐ +│ 󰨑 󰦖  ELK-3628 Create new menu.  Anna P. 󰵮 Petrov A. 󰙨 Ivanov I. +│ 󰨑 󰦕  XF-12 Change input hover.  Alex K. 󰵮 Ivanov I. 󰙨 Petrov A., Petrov B. +│ 󰨑 󱞇  ELK-3628 Crete new project with menu, profile, issues and #  Anna P. 󰵮 Ivanov I. 󰙨 Petrov A., Petrov B. +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + + + publish +󱓞 release +󰶯 hotfix +󱓞 release candidate + +┌─────────────────────────────────────────────────────────────────────────── Actual releases 2023 ─────────────────────────────────────────────────────────┐ +│ Jan Feb Mar Apl May Jun Jul Aug Sep Oct Nov Dec +│ ├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ LK 󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞 +│ CP 󱓞  󱓞  󱓞  󱓞    󱓞  󱓞  󱓞  󱓞  󱓞  󱓞  󱓞 +│ RR 󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯 +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + + GA4  Jaeger 󰡾 Fallback  Screenshots  Connected  Auth +Я YM  Prometheus 󰉼 Renovate  End2End  Disconnected 󰖟 WWW +󰩃 Husky 󱎴 Sentry 󱉕 Lighthouse  Unit  Not implemented 󰚩 SEO + Probs  Grafana 󱂅 Kibana  Coverage  No need + +┌─────────────────────────────────────────────────────────────────────────── Tools ──────────────────────────────────┐ +│   Я  󰉼 󰩃 󱎴 󱉕   󱂅     󰖟 󰚩  󰡾 +│ elk-site                    +└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 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/TUI.Engine/Attributes/Alignments/Alignment.cs b/src/TUI.Engine/Attributes/Alignments/Alignment.cs new file mode 100644 index 0000000..ffa9430 --- /dev/null +++ b/src/TUI.Engine/Attributes/Alignments/Alignment.cs @@ -0,0 +1,3 @@ +namespace TUI.Engine.Attributes.Alignments; + +public record Alignment(Horizontal Horizontal, Vertical Vertical); \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Alignments/Horizontal.cs b/src/TUI.Engine/Attributes/Alignments/Horizontal.cs new file mode 100644 index 0000000..10e010d --- /dev/null +++ b/src/TUI.Engine/Attributes/Alignments/Horizontal.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Attributes.Alignments; + +public enum Horizontal +{ + Left = 0, + Center = 1, + Right = 2, +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Alignments/IWithAlignment.cs b/src/TUI.Engine/Attributes/Alignments/IWithAlignment.cs new file mode 100644 index 0000000..b8272e5 --- /dev/null +++ b/src/TUI.Engine/Attributes/Alignments/IWithAlignment.cs @@ -0,0 +1,10 @@ +namespace TUI.Engine.Attributes.Alignments; + +public interface IWithAlignment +{ + internal Alignment Alignment { get; } + + void SetAlignment(Vertical vertical); + + void SetAlignment(Horizontal horizontal); +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Alignments/Vertical.cs b/src/TUI.Engine/Attributes/Alignments/Vertical.cs new file mode 100644 index 0000000..9c26c2d --- /dev/null +++ b/src/TUI.Engine/Attributes/Alignments/Vertical.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Attributes.Alignments; + +public enum Vertical +{ + Top, + Center, + Bottom, +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Orientations/IWithOrientation.cs b/src/TUI.Engine/Attributes/Orientations/IWithOrientation.cs new file mode 100644 index 0000000..6b379f3 --- /dev/null +++ b/src/TUI.Engine/Attributes/Orientations/IWithOrientation.cs @@ -0,0 +1,10 @@ +namespace TUI.Engine.Attributes.Orientations; + +public interface IWithOrientation +{ + internal Orientation Orientation { get; } + + public void SetOrientationHorizontal(); + + public void SetOrientationVertical(); +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Orientations/Orientation.cs b/src/TUI.Engine/Attributes/Orientations/Orientation.cs new file mode 100644 index 0000000..66f3a1f --- /dev/null +++ b/src/TUI.Engine/Attributes/Orientations/Orientation.cs @@ -0,0 +1,7 @@ +namespace TUI.Engine.Attributes.Orientations; + +public enum Orientation +{ + Horizontal, + Vertical, +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Paddings/IWithPadding.cs b/src/TUI.Engine/Attributes/Paddings/IWithPadding.cs new file mode 100644 index 0000000..453ea7e --- /dev/null +++ b/src/TUI.Engine/Attributes/Paddings/IWithPadding.cs @@ -0,0 +1,14 @@ +using TUI.Engine.Theme; + +namespace TUI.Engine.Attributes.Paddings; + +public interface IWithPadding +{ + Padding Padding { get; } + + void SetPadding(Level level); + void SetPaddingLeft(Level level); + void SetPaddingTop(Level level); + void SetPaddingBottom(Level level); + void SetPaddingRight(Level level); +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Paddings/Padding.cs b/src/TUI.Engine/Attributes/Paddings/Padding.cs new file mode 100644 index 0000000..ac97f6e --- /dev/null +++ b/src/TUI.Engine/Attributes/Paddings/Padding.cs @@ -0,0 +1,15 @@ +using TUI.Engine.Theme; + +namespace TUI.Engine.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/Attributes/Resizings/IResizable.cs b/src/TUI.Engine/Attributes/Resizings/IResizable.cs new file mode 100644 index 0000000..2e28b18 --- /dev/null +++ b/src/TUI.Engine/Attributes/Resizings/IResizable.cs @@ -0,0 +1,16 @@ +using TUI.Engine.Attributes.Orientations; + +namespace TUI.Engine.Attributes.Resizings; + +public interface IResizable +{ + internal Resizing ResizingHorizontal { get; } + + internal Resizing ResizingVertical { get; } + + internal Size GetFixedSize(); + + void SetAdaptive(Orientation orientation); + + void SetFixed(Orientation orientation, int value); +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Resizings/Resizing.cs b/src/TUI.Engine/Attributes/Resizings/Resizing.cs new file mode 100644 index 0000000..163ac95 --- /dev/null +++ b/src/TUI.Engine/Attributes/Resizings/Resizing.cs @@ -0,0 +1,7 @@ +namespace TUI.Engine.Attributes.Resizings; + +public enum Resizing +{ + Adaptive, + Fixed +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Resizings/ResizingExtensions.cs b/src/TUI.Engine/Attributes/Resizings/ResizingExtensions.cs new file mode 100644 index 0000000..df50d2e --- /dev/null +++ b/src/TUI.Engine/Attributes/Resizings/ResizingExtensions.cs @@ -0,0 +1,60 @@ +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Containers; + +namespace TUI.Engine.Attributes.Resizings; + +internal static class ResizingExtensions +{ + internal static int GetHeight(this IResizable node, IContainer container, int maxHeight, int nodeIndex) + { + if (node.ResizingVertical == Resizing.Fixed) + { + return node.GetFixedSize().Height; + } + + if (container.Orientation == Orientation.Horizontal) + { + return maxHeight; + } + + var fixedNodes = container.GetFixedNodes().ToArray(); + var absoluteNodes = container.GetAbsoluteNodes().ToArray(); + + var fixedHeight = fixedNodes.Sum(s => s.GetFixedSize().Height); + var allowableHeight = maxHeight - fixedHeight; + + var allowableCount = container.GetNodes().Count - fixedNodes.Length - absoluteNodes.Length; + var nodeHeight = (allowableHeight / allowableCount).Min(1); + var nodeNumber = nodeIndex + 1 - container.GetFixedNodes(nodeIndex).Count() - + container.GetAbsoluteNodes(nodeIndex).Count(); + + if (allowableHeight - nodeNumber * nodeHeight < nodeHeight) + { + return allowableHeight + nodeHeight - nodeNumber * nodeHeight; + } + + return nodeHeight; + } + + internal static int GetWidth(this IResizable node, IContainer container, int maxWidth) + { + if (node.ResizingHorizontal == Resizing.Fixed) + { + return node.GetFixedSize().Width; + } + + if (container.Orientation == Orientation.Vertical) + { + return maxWidth; + } + + var fixedNodes = container + .GetNodes() + .Where(n => n.ResizingHorizontal == Resizing.Fixed).ToArray(); + + var allowableWidth = maxWidth - fixedNodes.Sum(s => s.GetFixedSize().Width); + var allowableCount = container.GetNodes().Count - fixedNodes.Length; + + return allowableWidth / allowableCount; + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Attributes/Size.cs b/src/TUI.Engine/Attributes/Size.cs new file mode 100644 index 0000000..6f74084 --- /dev/null +++ b/src/TUI.Engine/Attributes/Size.cs @@ -0,0 +1,10 @@ +using TUI.Engine.Nodes; + +namespace TUI.Engine.Attributes; + +public readonly record struct Size(int Width, int Height) +{ + public static Size operator -(Size a, Position b) => new(a.Width - b.Left, a.Height - b.Top); + + public override string ToString() => $"W[{Width}] H[{Height}]"; +} \ No newline at end of file diff --git a/src/TUI.Engine/Components/ComponentBase.cs b/src/TUI.Engine/Components/ComponentBase.cs new file mode 100644 index 0000000..1b37294 --- /dev/null +++ b/src/TUI.Engine/Components/ComponentBase.cs @@ -0,0 +1,56 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Attributes.Paddings; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Engine.Components; + +public abstract class ComponentBase : NodeBase, IComponent +{ + protected abstract Sketch DrawComponent(Size minSize); + + public bool IsRelative { get; private set; } = true; + + public void SetRelative() + { + IsRelative = true; + } + + public void SetAbsolute() + { + IsRelative = false; + } + + Sketch IComponent.MakeSketch(Size minSize) => DrawComponent(minSize); + + #region Alignments + + Alignment IWithAlignment.Alignment => Alignment; + + internal Alignment Alignment { get; private set; } = new(Defaults.HorizontalAlignment, Defaults.VerticalAlignment); + + public void SetAlignment(Vertical vertical) => Alignment = Alignment with { Vertical = vertical }; + + public void SetAlignment(Horizontal horizontal) => Alignment = Alignment with { Horizontal = horizontal }; + + #endregion + + #region Paddings + + Padding IWithPadding.Padding => Padding; + + internal Padding Padding { get; private set; } = new(Defaults.Padding); + + public void SetPadding(Level level) => Padding = new Padding(level); + + public void SetPaddingTop(Level level) => Padding = Padding with { Top = level }; + + public void SetPaddingRight(Level level) => Padding = Padding with { Right = level }; + + public void SetPaddingBottom(Level level) => Padding = Padding with { Bottom = level }; + + public void SetPaddingLeft(Level level) => Padding = Padding with { Left = level }; + + #endregion +} \ No newline at end of file diff --git a/src/TUI.Engine/Components/ComponentExtensions.cs b/src/TUI.Engine/Components/ComponentExtensions.cs new file mode 100644 index 0000000..6c5e4ca --- /dev/null +++ b/src/TUI.Engine/Components/ComponentExtensions.cs @@ -0,0 +1,40 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Components; + +internal static class ComponentExtensions +{ + internal static Position CorrectContentPosition(this IComponent component, + Position pencil, + Size maxSize, + Size sketchSize) + { + var padding = component.Padding; + var alignment = component.Alignment; + var alignmentCompensationLeft = GetAlignmentCompensationLeft(alignment.Horizontal, maxSize, sketchSize); + var alignmentCompensationTop = GetAlignmentCompensationTop(alignment.Vertical, maxSize, sketchSize); + var left = pencil.Left + (int)padding.Left + alignmentCompensationLeft; + var top = pencil.Top + (int)padding.Top + alignmentCompensationTop; + return new Position(left, top); + } + + private static int GetAlignmentCompensationLeft(Horizontal alignment, Size maxSize, Size sketchSize) => + alignment switch + { + Horizontal.Left => 0, + Horizontal.Center => (maxSize.Width - sketchSize.Width) / 2, + Horizontal.Right => maxSize.Width - sketchSize.Width, + _ => 0 + }; + + private static int GetAlignmentCompensationTop(Vertical alignment, Size maxSize, Size sketchSize) => + alignment switch + { + Vertical.Top => 0, + Vertical.Center => (maxSize.Height - sketchSize.Height) / 2, + Vertical.Bottom => maxSize.Height - sketchSize.Height, + _ => 0 + }; +} \ No newline at end of file diff --git a/src/TUI.Engine/Components/IComponent.cs b/src/TUI.Engine/Components/IComponent.cs new file mode 100644 index 0000000..aa79ee7 --- /dev/null +++ b/src/TUI.Engine/Components/IComponent.cs @@ -0,0 +1,17 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Attributes.Paddings; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Components; + +public interface IComponent : INode, IWithAlignment, IWithPadding +{ + internal bool IsRelative { get; } + + public void SetRelative(); + + public void SetAbsolute(); + + internal Sketch MakeSketch(Size minSize); +} \ No newline at end of file diff --git a/src/TUI.Engine/Components/Sketch.cs b/src/TUI.Engine/Components/Sketch.cs new file mode 100644 index 0000000..ed96902 --- /dev/null +++ b/src/TUI.Engine/Components/Sketch.cs @@ -0,0 +1,31 @@ +using System.Text; +using TUI.Engine.Attributes; + +namespace TUI.Engine.Components; + +public sealed class Sketch : IEnumerable +{ + private IEnumerable ContentRows { get; } + + public Sketch(string content) => ContentRows = content.Split(Symbols.LineBreak); + + public Sketch(StringBuilder builder) => ContentRows = builder.ToString().Split(Symbols.LineBreak); + + public IEnumerator GetEnumerator() => ContentRows.GetEnumerator(); + + public IEnumerable Crop(Size maxSize) => ContentRows + .Select(row => maxSize.Width < row.GetWidth() ? row[..maxSize.Width] : row) + .Take(maxSize.Height) + .ToArray(); + + public Size GetSize() + { + var width = ContentRows.Select(row => row.GetWidth()).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/Components/StaticComponentBase.cs b/src/TUI.Engine/Components/StaticComponentBase.cs new file mode 100644 index 0000000..009d388 --- /dev/null +++ b/src/TUI.Engine/Components/StaticComponentBase.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine.Attributes; + +namespace TUI.Engine.Components; + +public abstract class StaticComponentBase : ComponentBase +{ + private Sketch? _cache; + + protected abstract void RenderWithCache(StringBuilder builder); + + protected override Sketch DrawComponent(Size sketchMinSize) + { + if (_cache is not null) + { + return _cache; + } + + var builder = new StringBuilder(); + + RenderWithCache(builder); + + _cache = new Sketch(builder.ToString()); + + return _cache; + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Containers/ContainerBase.cs b/src/TUI.Engine/Containers/ContainerBase.cs new file mode 100644 index 0000000..e2b5141 --- /dev/null +++ b/src/TUI.Engine/Containers/ContainerBase.cs @@ -0,0 +1,24 @@ +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Engine.Containers; + +public abstract class ContainerBase : NodeBase, IContainer +{ + private Orientation _orientation = Defaults.Orientation; + + Orientation IWithOrientation.Orientation => _orientation; + + public void SetOrientationHorizontal() + { + _orientation = Orientation.Horizontal; + } + + public void SetOrientationVertical() + { + _orientation = Orientation.Vertical; + } + + public abstract Nodes.Nodes GetNodes(); +} \ No newline at end of file diff --git a/src/TUI.Engine/Containers/ContainerExtensions.cs b/src/TUI.Engine/Containers/ContainerExtensions.cs new file mode 100644 index 0000000..a53270d --- /dev/null +++ b/src/TUI.Engine/Containers/ContainerExtensions.cs @@ -0,0 +1,42 @@ +using TUI.Engine.Attributes.Resizings; +using TUI.Engine.Components; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Containers; + +internal static class ContainerExtensions +{ + private static readonly Func AbsoluteNodes = node => node is IComponent { IsRelative: false }; + + private static readonly Func FixedNodes = node => node.ResizingVertical == Resizing.Fixed; + + internal static IEnumerable GetAbsoluteNodes(this IContainer container, int? takeNodeNumber = null) + { + if (takeNodeNumber is not null) + { + return container + .GetNodes() + .Take(takeNodeNumber.Value + 1) + .Where(AbsoluteNodes); + } + + return container + .GetNodes() + .Where(AbsoluteNodes); + } + + internal static IEnumerable GetFixedNodes(this IContainer container, int? takeNodeNumber = null) + { + if (takeNodeNumber is not null) + { + return container + .GetNodes() + .Take(takeNodeNumber.Value + 1) + .Where(FixedNodes); + } + + return container + .GetNodes() + .Where(FixedNodes); + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Containers/IContainer.cs b/src/TUI.Engine/Containers/IContainer.cs new file mode 100644 index 0000000..b195750 --- /dev/null +++ b/src/TUI.Engine/Containers/IContainer.cs @@ -0,0 +1,9 @@ +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Containers; + +public interface IContainer : INode, IWithOrientation +{ + public Nodes.Nodes GetNodes(); +} \ No newline at end of file diff --git a/src/TUI.Engine/Extensions.cs b/src/TUI.Engine/Extensions.cs new file mode 100644 index 0000000..9ef3c11 --- /dev/null +++ b/src/TUI.Engine/Extensions.cs @@ -0,0 +1,51 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace TUI.Engine; + +public static class Extensions +{ + public static int Max(this int value, int maxValue) + { + return value <= maxValue ? value : maxValue; + } + + public static int Min(this int value, int minValue) + { + return value > minValue ? value : minValue; + } + + public static bool Have(this IEnumerable array, string findValue) + { + return array.Any(item => item == findValue); + } + + public static string Repeat(this string value, int count) + { + return count < 0 ? string.Empty : new StringBuilder(value.Length * count).Insert(0, value, count).ToString(); + } + + public static string RemoveColors(this string text) + { + return Regex.Replace(text, @"\S\[(\d{0,3}[;m]_?){0,5}", ""); + } + + public static int GetWidth(this string text) + { + if (string.IsNullOrEmpty(text)) return 0; + + var clearText = text.RemoveColors(); + var stringInfo = new StringInfo(clearText); + return stringInfo.LengthInTextElements; + } + + public static Version ToVersion(this string textVersion) + { + var version = textVersion.Replace("^", "").Replace("~", "").Split("."); + var major = Convert.ToInt32(version[0]); + var minor = Convert.ToInt32(version[1]); + var patch = Convert.ToInt32(version[2].Split('-')[0]); + return new Version(major, minor, patch); + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Helper.cs b/src/TUI.Engine/Helper.cs new file mode 100644 index 0000000..b63adc9 --- /dev/null +++ b/src/TUI.Engine/Helper.cs @@ -0,0 +1,55 @@ +using System.Runtime.CompilerServices; +using Pastel; +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; + +[assembly: InternalsVisibleTo("TUI.Engine.Tests", AllInternalsVisible = true)] + +namespace TUI.Engine; + +public static class Helper +{ + private static readonly Queue Colors = new(); + + private static void Init() + { + 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(Position position, Size size) + { + return; + if (!Colors.Any()) + { + Init(); + } + + 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/INode.cs b/src/TUI.Engine/Nodes/INode.cs new file mode 100644 index 0000000..02e87ed --- /dev/null +++ b/src/TUI.Engine/Nodes/INode.cs @@ -0,0 +1,15 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Resizings; +using TUI.Engine.Rendering.Canvas; + +namespace TUI.Engine.Nodes; + +public interface INode : IResizable +{ + DrawContext DrawContext { get; set; } + StyleContext StyleContext { get; set; } +} + +public record DrawContext(ICanvas Canvas, Position Pencil, Size MaxSize); + +public record StyleContext(string? Foreground, string? Background = null); \ No newline at end of file diff --git a/src/TUI.Engine/Nodes/NodeBase.cs b/src/TUI.Engine/Nodes/NodeBase.cs new file mode 100644 index 0000000..30e88cd --- /dev/null +++ b/src/TUI.Engine/Nodes/NodeBase.cs @@ -0,0 +1,62 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Attributes.Resizings; +using TUI.Engine.Theme; + +namespace TUI.Engine.Nodes; + +public abstract class NodeBase : INode +{ + private int _fixedWidth; + private int _fixedHeight; + + Size IResizable.GetFixedSize() => new(_fixedWidth, _fixedHeight); + + #region Resizing + + private Resizing ResizingHorizontal { get; set; } = Defaults.HorizontalResizing; + + Resizing IResizable.ResizingHorizontal => ResizingHorizontal; + + private Resizing ResizingVertical { get; set; } = Defaults.VerticalResizing; + + Resizing IResizable.ResizingVertical => ResizingVertical; + + public void SetAdaptive(Orientation orientation) + { + switch (orientation) + { + case Orientation.Horizontal: + ResizingHorizontal = Resizing.Adaptive; + break; + case Orientation.Vertical: + ResizingVertical = Resizing.Adaptive; + break; + default: + throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); + } + } + + public void SetFixed(Orientation orientation, int value) + { + switch (orientation) + { + case Orientation.Horizontal: + ResizingHorizontal = Resizing.Fixed; + _fixedWidth = value; + break; + case Orientation.Vertical: + ResizingVertical = Resizing.Fixed; + _fixedHeight = value; + break; + default: + throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); + } + } + + #endregion Resizing + + public DrawContext? DrawContext { get; set; } + + public StyleContext? StyleContext { get; set; } +} \ No newline at end of file diff --git a/src/TUI.Engine/Nodes/NodeExtensions.cs b/src/TUI.Engine/Nodes/NodeExtensions.cs new file mode 100644 index 0000000..efdb506 --- /dev/null +++ b/src/TUI.Engine/Nodes/NodeExtensions.cs @@ -0,0 +1,15 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Resizings; +using TUI.Engine.Containers; + +namespace TUI.Engine.Nodes; + +internal static class NodeExtensions +{ + internal static Size GetSize(this INode node, IContainer parentContainer, int nodeNumber, Size allowableSize) + { + var width = node.GetWidth(parentContainer, allowableSize.Width); + var height = node.GetHeight(parentContainer, allowableSize.Height, nodeNumber); + return new Size(width, height); + } +} \ 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/Nodes/Position.cs b/src/TUI.Engine/Nodes/Position.cs new file mode 100644 index 0000000..bc566d2 --- /dev/null +++ b/src/TUI.Engine/Nodes/Position.cs @@ -0,0 +1,8 @@ +namespace TUI.Engine.Nodes; + +public record Position(int Left, int Top) +{ + public static readonly Position Default = new(0, 0); + + public override string ToString() => $"L[{Left}] T[{Top}]"; +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/Canvas/ConsoleCanvas.cs b/src/TUI.Engine/Rendering/Canvas/ConsoleCanvas.cs new file mode 100644 index 0000000..9313148 --- /dev/null +++ b/src/TUI.Engine/Rendering/Canvas/ConsoleCanvas.cs @@ -0,0 +1,35 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering.Canvas; + +public class ConsoleCanvas : ICanvas +{ + private readonly DrawCraftsman _drawCraftsman; + + public Size Size { get; } = new(Console.WindowWidth, Console.WindowHeight); + + public ConsoleCanvas() + { + var componentCraftsman = new ComponentCraftsman(this); + var containerCraftsman = new ContainerCraftsman(componentCraftsman); + _drawCraftsman = new DrawCraftsman(componentCraftsman, containerCraftsman); + } + + public void SetPencil(Position pencilPosition) + { + Console.SetCursorPosition(pencilPosition.Left, pencilPosition.Top); + } + + public void Paint(string value) => Console.Write(value); + + public void Draw(INode node) + { + _drawCraftsman.Draw(node, Position.Default, Size); + } + + public void Draw(INode node, Position pencil, Size maxSize) + { + _drawCraftsman.Draw(node, pencil, maxSize); + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/Canvas/ICanvas.cs b/src/TUI.Engine/Rendering/Canvas/ICanvas.cs new file mode 100644 index 0000000..efe3efe --- /dev/null +++ b/src/TUI.Engine/Rendering/Canvas/ICanvas.cs @@ -0,0 +1,17 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering.Canvas; + +public interface ICanvas +{ + Size Size { get; } + + void SetPencil(Position pencilPosition); + + void Paint(string value); + + void Draw(INode node); + + void Draw(INode node, Position pencil, Size maxSize); +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/ComponentCraftsman.cs b/src/TUI.Engine/Rendering/ComponentCraftsman.cs new file mode 100644 index 0000000..a24cba9 --- /dev/null +++ b/src/TUI.Engine/Rendering/ComponentCraftsman.cs @@ -0,0 +1,42 @@ +using Pastel; +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering.Canvas; + +namespace TUI.Engine.Rendering; + +internal sealed class ComponentCraftsman : CraftsmanBase, IDrawable +{ + private readonly ICanvas _canvas; + + public ComponentCraftsman(ICanvas canvas) + { + _canvas = canvas; + } + + public Size Draw(IComponent component, Position pencil, Size maxSize) + { + var sketch = component.MakeSketch(maxSize); + var sketchSize = sketch.GetSize(); + + var correctedPencil = component.CorrectContentPosition(pencil, maxSize, sketchSize); + + Debug(pencil, maxSize); + + foreach (var line in sketch.Crop(maxSize)) + { + component.DrawContext = new DrawContext(_canvas, pencil, maxSize); + + _canvas.SetPencil(correctedPencil); + _canvas.Paint(component.StyleContext is not null + ? line.RemoveColors().Pastel(component.StyleContext.Foreground) + : line); + + + correctedPencil = correctedPencil with { Top = correctedPencil.Top + 1 }; + } + + return sketchSize; + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/ContainerCraftsman.cs b/src/TUI.Engine/Rendering/ContainerCraftsman.cs new file mode 100644 index 0000000..74b4601 --- /dev/null +++ b/src/TUI.Engine/Rendering/ContainerCraftsman.cs @@ -0,0 +1,86 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Attributes.Resizings; +using TUI.Engine.Components; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering; + +internal sealed class ContainerCraftsman : CraftsmanBase, IDrawable +{ + private readonly IDrawable _componentCraftsman; + + public ContainerCraftsman(IDrawable componentCraftsman) + { + _componentCraftsman = componentCraftsman; + } + + public Size Draw(IContainer container, Position pencil, Size maxSize) + { + var nodeNumber = 0; + var nextNodePosition = pencil; + var nodes = container.GetNodes(); + + Debug(nextNodePosition, maxSize); + + while (nodeNumber < nodes.Count) + { + var node = nodes[nodeNumber]; + var nodeSize = node.GetSize(container, nodeNumber, maxSize); + + nextNodePosition = DrawNode(node, container, nextNodePosition, nodeSize); + nodeNumber++; + } + + return maxSize; + } + + private Position DrawNode(INode node, IContainer container, Position nodePosition, Size maxSize) + { + switch (node) + { + case IContainer childContainer: + var containerSize = Draw(childContainer, nodePosition, maxSize); + return GetNextNodePosition(container, containerSize, nodePosition); + case IComponent childComponent: + var componentSize = _componentCraftsman.Draw(childComponent, nodePosition, maxSize); + return childComponent.IsRelative + ? GetNextNodePosition(container, maxSize, nodePosition, componentSize) + : nodePosition; + default: + throw new InvalidCastException(); + } + } + + private static Position GetNextNodePosition( + IContainer container, + Size defaultSize, + Position position, + Size? componentSize = null) + { + switch (container.Orientation) + { + case Orientation.Horizontal: + var componentWidth = container.ResizingHorizontal switch + { + Resizing.Adaptive => defaultSize.Width, + Resizing.Fixed => componentSize?.Width.Max(container.GetFixedSize().Width) ?? + container.GetFixedSize().Width, + _ => 0 + }; + return position with { Left = position.Left + componentWidth }; + case Orientation.Vertical: + var componentHeight = container.ResizingVertical switch + { + Resizing.Adaptive => defaultSize.Height, + Resizing.Fixed => componentSize?.Height.Max(container.GetFixedSize().Height) ?? + container.GetFixedSize().Height, + _ => 0 + }; + return position with { Top = position.Top + componentHeight }; + default: + throw new InvalidCastException(); + } + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/CraftsmanBase.cs b/src/TUI.Engine/Rendering/CraftsmanBase.cs new file mode 100644 index 0000000..a411d86 --- /dev/null +++ b/src/TUI.Engine/Rendering/CraftsmanBase.cs @@ -0,0 +1,14 @@ +using System.Diagnostics; +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering; + +public abstract class CraftsmanBase +{ + protected void Debug(Position pencilPosition, Size allowableSize) + { + Debugger.Log(0, "Draw", $"{pencilPosition}{GetType().Name}.\n"); + Helper.ShowBackground(pencilPosition, allowableSize); + } +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/IDrawable.cs b/src/TUI.Engine/Rendering/IDrawable.cs new file mode 100644 index 0000000..dcf7ff0 --- /dev/null +++ b/src/TUI.Engine/Rendering/IDrawable.cs @@ -0,0 +1,9 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering; + +public interface IDrawable where TItem : INode +{ + Size Draw(TItem item, Position pencil, Size maxSize); +} \ No newline at end of file diff --git a/src/TUI.Engine/Rendering/NodeCraftsman.cs b/src/TUI.Engine/Rendering/NodeCraftsman.cs new file mode 100644 index 0000000..ccce935 --- /dev/null +++ b/src/TUI.Engine/Rendering/NodeCraftsman.cs @@ -0,0 +1,28 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Rendering; + +internal sealed class DrawCraftsman : IDrawable +{ + private readonly IDrawable _componentCraftsman; + private readonly IDrawable _containerCraftsman; + + public DrawCraftsman( + IDrawable componentCraftsman, + IDrawable containerCraftsman) + { + _componentCraftsman = componentCraftsman; + _containerCraftsman = containerCraftsman; + } + + public Size Draw(INode node, Position pencil, Size maxSize) => + node switch + { + IContainer container => _containerCraftsman.Draw(container, pencil, maxSize), + IComponent component => _componentCraftsman.Draw(component, pencil, maxSize), + _ => throw new InvalidCastException("Unknown node type.") + }; +} \ No newline at end of file diff --git a/src/TUI.Engine/SymbolExtensions.cs b/src/TUI.Engine/SymbolExtensions.cs new file mode 100644 index 0000000..9454063 --- /dev/null +++ b/src/TUI.Engine/SymbolExtensions.cs @@ -0,0 +1,24 @@ +using Pastel; +using TUI.Engine.Theme; + +namespace TUI.Engine; + +public static class SymbolExtensions +{ + public static string Colorized(this string symbol) => + !SymbolColors.ContainsKey(symbol) ? symbol.Hint() : symbol.Pastel(SymbolColors[symbol]); + + private static readonly Dictionary SymbolColors = new() + { + { Symbols.Git, "F14E32" }, + { Symbols.Site, "BF40BF" }, + { Symbols.GitLab, "E24329" }, + { Symbols.GitHub, "ADBAC7" }, + { Symbols.NetworkPublic, "00FFFF" }, + { Symbols.Api, "7F52FF" }, + { Symbols.DockerImage, "086DD7" }, + { Symbols.NpmPackage, "CB0000" }, + { Symbols.Seo, "4285F4" }, + { Symbols.Auth, "FFD700" }, + }; +} \ 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..654907f --- /dev/null +++ b/src/TUI.Engine/Symbols.cs @@ -0,0 +1,37 @@ +namespace TUI.Engine; + +public static class Symbols +{ + public const string Space = " "; + public const string Copyright = "©"; + public const string GitLab = ""; + public const string GitHub = ""; + public const string Git = ""; + public const string LineBreak = "\n"; + public const string NetworkPublic = "󰞉"; + public const string NetworkPrivate = "󰕑"; + public const string Undefined = ""; + public const string Site = ""; + public const string Api = ""; + public const string DockerImage = ""; + public const string NpmPackage = ""; + public const string Seo = "󰚩"; + public const string Auth = ""; + public const string NotFound = ""; + public const string Error = ""; + public const string Download = ""; + + public static class Lines + { + public const string Vertical = "│"; + public const string Horizontal = "─"; + } + + public static class Angles + { + public const string RightTop = "┐"; + public const string LeftBottom = "└"; + public const string LeftTop = "┌"; + public const string RightBottom = "┘"; + } +} \ No newline at end of file diff --git a/src/TUI.Engine/TUI.Engine.csproj b/src/TUI.Engine/TUI.Engine.csproj new file mode 100644 index 0000000..5e00211 --- /dev/null +++ b/src/TUI.Engine/TUI.Engine.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + + diff --git a/src/TUI.Engine/Theme/Defaults.cs b/src/TUI.Engine/Theme/Defaults.cs new file mode 100644 index 0000000..b22e9d0 --- /dev/null +++ b/src/TUI.Engine/Theme/Defaults.cs @@ -0,0 +1,20 @@ +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Attributes.Resizings; + +namespace TUI.Engine.Theme; + +public static class Defaults +{ + public const Horizontal HorizontalAlignment = Horizontal.Center; + + public const Vertical VerticalAlignment = Vertical.Top; + + public const Level Padding = Level.None; + + public const Resizing HorizontalResizing = Resizing.Adaptive; + + public const Resizing VerticalResizing = Resizing.Adaptive; + + public const Orientation Orientation = TUI.Engine.Attributes.Orientations.Orientation.Horizontal; +} \ No newline at end of file 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/UserInterface/Palette.cs b/src/TUI.Engine/Theme/Palette.cs similarity index 57% rename from src/TUI/UserInterface/Palette.cs rename to src/TUI.Engine/Theme/Palette.cs index c561172..8f9b7ea 100644 --- a/src/TUI/UserInterface/Palette.cs +++ b/src/TUI.Engine/Theme/Palette.cs @@ -1,21 +1,35 @@ using Pastel; - -namespace TUI.UserInterface; +namespace TUI.Engine.Theme; public static class Palette { public const string HoverColor = "292928"; public const string PrimaryColor = "84BA64"; public const string HintColor = "71797E"; + public const string DisableColor = "303030"; public const string ErrorColor = "CA3433"; public const string WarningColor = "EC9706"; public const string InfoColor = "25799F"; - - public static string Primary(this string currentText) => currentText.Pastel(PrimaryColor); + + 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 Hint(this char currentText) => currentText.ToString().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); + + public static string Info(this char currentText) => currentText.ToString().Pastel(InfoColor); + + public static string Info(this int currentText) => currentText.ToString().Pastel(InfoColor); } \ No newline at end of file diff --git a/src/TUI/Controls/Common/StubComponent.cs b/src/TUI/Controls/Common/StubComponent.cs new file mode 100644 index 0000000..a3c39e9 --- /dev/null +++ b/src/TUI/Controls/Common/StubComponent.cs @@ -0,0 +1,40 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Components; + +namespace TUI.Controls.Common; + +public class StubComponent : ComponentBase +{ + private readonly Size _size; + private readonly string? _text; + + public StubComponent(Size size, string? text = null) + { + _size = size; + _text = text; + SetFixed(Orientation.Horizontal, size.Width); + SetFixed(Orientation.Vertical, size.Height); + } + + protected override Sketch DrawComponent(Size minSize) + { + var builder = new StringBuilder(); + var height = 0; + + + while (_size.Height > height) + { + builder.Append(Symbols.Space.Repeat(_size.Width - (_text?.GetWidth() ?? 0))); + height++; + } + + if (_text is not null) + { + builder.Append(_text); + } + return new Sketch(builder.ToString()); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/BreadCrumbsComponent.cs b/src/TUI/Controls/Components/BreadCrumbsComponent.cs new file mode 100644 index 0000000..0d230a5 --- /dev/null +++ b/src/TUI/Controls/Components/BreadCrumbsComponent.cs @@ -0,0 +1,20 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Components; + +public class BreadCrumbsComponent : ComponentBase +{ + private readonly List _crumbs = new() { " " }; + + public BreadCrumbsComponent(params string[] crumbs) + { + _crumbs.AddRange(crumbs); + } + + protected override Sketch DrawComponent(Size minSize) + { + return new Sketch(string.Join("  ".Hint(), _crumbs).Hint()); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/CellsComponentBase.cs b/src/TUI/Controls/Components/CellsComponentBase.cs new file mode 100644 index 0000000..a50685c --- /dev/null +++ b/src/TUI/Controls/Components/CellsComponentBase.cs @@ -0,0 +1,36 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Components; + +namespace TUI.Controls.Components; + +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.GetWidth())); + content.Append(cell); + } + + // base.Render(content, position, size); + } + + protected override Sketch DrawComponent(Size minSize) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/PanelComponent.cs b/src/TUI/Controls/Components/PanelComponent.cs new file mode 100644 index 0000000..9bb9d5c --- /dev/null +++ b/src/TUI/Controls/Components/PanelComponent.cs @@ -0,0 +1,67 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Theme; +using static TUI.Engine.Symbols; + +namespace TUI.Controls.Components; + +public class PanelComponent : ComponentBase, IComponent +{ + private readonly string _title; + + public PanelComponent(string title) + { + _title = title; + SetAbsolute(); + } + + private static void RenderTopLine(StringBuilder builder, Size size, string title) + { + var halfWidth = (size.Width - title.GetWidth() - (int)Indentation.BorderWidth * 2 - + (int)Indentation.Default * 2) / 2; + builder.Append(Angles.LeftTop); + builder.Append(Lines.Horizontal.Repeat(halfWidth)); + builder.AppendFormat("{0}{1}{0}", Space.Repeat(Convert.ToInt32(Indentation.Default)), title); + builder.Append(Lines.Horizontal.Repeat(halfWidth + halfWidth % 2)); + builder.Append(Angles.RightTop); + builder.Append(LineBreak); + } + + private static void RenderMiddleLine(StringBuilder builder, Size size) + { + var dashboardHeight = size.Height - (int)Indentation.BorderWidth * 2; + + while (dashboardHeight > 0) + { + var bodyWidth = size.Width - (int)Indentation.BorderWidth * 2; + builder.Append(Lines.Vertical); + builder.Append(Space.Repeat(bodyWidth)); + builder.Append(Lines.Vertical); + builder.Append(LineBreak); + + dashboardHeight--; + } + } + + private static void RenderBottomLine(StringBuilder builder, Size size) + { + var width = size.Width - (int)Indentation.BorderWidth * 2; + builder.Append(Angles.LeftBottom); + builder.Append(Lines.Horizontal.Repeat(width)); + builder.Append(Angles.RightBottom); + builder.Append(LineBreak); + } + + protected override Sketch DrawComponent(Size minSize) + { + var builder = new StringBuilder(); + + RenderTopLine(builder, minSize, _title); + RenderMiddleLine(builder, minSize); + RenderBottomLine(builder, minSize); + + return new Sketch(builder.ToString().Main()); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/ProjectTitle.cs b/src/TUI/Controls/Components/ProjectTitle.cs new file mode 100644 index 0000000..a5f9559 --- /dev/null +++ b/src/TUI/Controls/Components/ProjectTitle.cs @@ -0,0 +1,47 @@ +using System.Text; +using TUI.Domain; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Theme; +using TUI.UserInterface; +using static TUI.Engine.Symbols; + +namespace TUI.Controls.Components; + +public class ProjectTitle : ComponentBase +{ + private readonly Project _project; + + public ProjectTitle(Project project) + { + _project = project; + } + + protected override Sketch DrawComponent(Size minSize) + { + var builder = new StringBuilder(); + builder.Append(GetHub().Colorized()); + builder.Append(Space); + builder.Append((_project.IsPublicNetwork ? NetworkPublic : NetworkPrivate).Colorized()); + builder.Append(Space); + builder.Append(_project.SeoDependent ? Seo.Colorized() : Seo.Disable()); + builder.Append(Space); + builder.Append(_project.HasAuth ? Auth.Colorized() : Auth.Disable()); + builder.Append(Space); + builder.Append(GetApplicationType().Colorized()); + builder.Append(Space); + builder.Append(_project.Name.Disable()); + return new Sketch(builder); + } + + private string GetHub() => _project.Hub.Type == "gitlab" ? GitLab : GitHub; + + private string GetApplicationType() + { + foreach (var application in Icons.Applications.Where(application => _project.Tags.Have(application.Value))) + return application.Key; + + return Undefined.Hint(); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/SpeakerComponent.cs b/src/TUI/Controls/Components/SpeakerComponent.cs new file mode 100644 index 0000000..1066978 --- /dev/null +++ b/src/TUI/Controls/Components/SpeakerComponent.cs @@ -0,0 +1,50 @@ +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Nodes; + +namespace TUI.Controls.Components; + +public class SpeakerComponent : ComponentBase +{ + private string _message = ""; + + private Position? _pencil; + + private SpeakerComponent() + { + } + + public static SpeakerComponent Instance { get; } = new(); + + protected override Sketch DrawComponent(Size minSize) + { + return new Sketch(_message); + } + + private void Clear() + { + _message = new string(' ', DrawContext.MaxSize.Width); + DrawContext.Canvas.Draw(Instance, _pencil, DrawContext.MaxSize); + } + + public void Shout(string emoji, string message) + { + if (DrawContext is null) + { + return; + } + + _pencil ??= DrawContext.Pencil with { }; + + Clear(); + _message = emoji + Symbols.Space + message; + DrawContext.Canvas.Draw(Instance, _pencil, DrawContext.MaxSize); + + Task.Delay(2000).ContinueWith(_ => + { + Clear(); + return Task.CompletedTask; + }); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/VersionComponent.cs b/src/TUI/Controls/Components/VersionComponent.cs new file mode 100644 index 0000000..d8016d4 --- /dev/null +++ b/src/TUI/Controls/Components/VersionComponent.cs @@ -0,0 +1,44 @@ +using System.Text; +using TUI.Domain; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Components; + +public class VersionComponent : ComponentBase +{ + private readonly VersionType _type; + private readonly string _version; + private readonly Brand? _brand; + private readonly VersionStatus _status; + + public VersionComponent(string version, Brand brand, VersionStatus status = VersionStatus.SoGood, + VersionType type = VersionType.Release) + { + _version = version; + _brand = brand; + _status = status; + _type = type; + } + + protected override Sketch DrawComponent(Size minSize) + { + var builder = new StringBuilder(); + + + if (_brand is not null) + { + builder.Append(_brand.ColorLogo()); + } + + builder.Append(_type.ToImage().Warning()); + builder.Append(Symbols.Space); + builder.Append(_version); + var sketch = builder.ToString(); + + return new Sketch(_status.Colorize(sketch)); + } +} + diff --git a/src/TUI/Controls/Components/VersionExtensions.cs b/src/TUI/Controls/Components/VersionExtensions.cs new file mode 100644 index 0000000..5de4afa --- /dev/null +++ b/src/TUI/Controls/Components/VersionExtensions.cs @@ -0,0 +1,28 @@ +using TUI.Engine.Theme; + +namespace TUI.Controls.Components; + +public static class VersionExtensions +{ + public static string ToImage(this VersionType versionType) + => + versionType switch + { + VersionType.Alpha => "󰀫", + VersionType.Beta => "󰂡", + VersionType.Candidate => "󰑣", + VersionType.Canary => "󱗆", + VersionType.Next => "󰒭", + _ => "" + }; + + public static string Colorize(this VersionStatus versionStatus, string value) => + versionStatus switch + { + VersionStatus.TooOld => value.Warning(), + VersionStatus.ToNew => value.Info(), + VersionStatus.SoGood => value.Hint(), + VersionStatus.BeNice => value.Main(), + _ => value + }; +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/VersionStatus.cs b/src/TUI/Controls/Components/VersionStatus.cs new file mode 100644 index 0000000..0974abe --- /dev/null +++ b/src/TUI/Controls/Components/VersionStatus.cs @@ -0,0 +1,10 @@ +namespace TUI.Controls.Components; + +public enum VersionStatus +{ + NotFound, + ToNew, + SoGood, + BeNice, + TooOld, +} \ No newline at end of file diff --git a/src/TUI/Controls/Components/VersionType.cs b/src/TUI/Controls/Components/VersionType.cs new file mode 100644 index 0000000..a7fac98 --- /dev/null +++ b/src/TUI/Controls/Components/VersionType.cs @@ -0,0 +1,12 @@ +namespace TUI.Controls.Components; + +public enum VersionType +{ + Convention, + Release, + Candidate, + Canary, + Alpha, + Beta, + Next, +} diff --git a/src/TUI/Controls/Containers/ContentContainer.cs b/src/TUI/Controls/Containers/ContentContainer.cs new file mode 100644 index 0000000..0eaa58a --- /dev/null +++ b/src/TUI/Controls/Containers/ContentContainer.cs @@ -0,0 +1,19 @@ +using TUI.Engine.Containers; +using TUI.Engine.Nodes; + +namespace TUI.Controls.Containers; + +public class ContentContainer : ContainerBase +{ + private readonly Nodes _children = new(); + + public void AddChildren(INode node) + { + _children.Add(node); + } + + public override Nodes GetNodes() + { + return _children; + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Containers/DashboardContainer.cs b/src/TUI/Controls/Containers/DashboardContainer.cs new file mode 100644 index 0000000..d775396 --- /dev/null +++ b/src/TUI/Controls/Containers/DashboardContainer.cs @@ -0,0 +1,34 @@ +using TUI.Controls.Components; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Controls.Containers; + +public class DashboardContainer : ContainerBase +{ + private readonly Nodes _children = new(); + private readonly ContentContainer _content; + + public DashboardContainer() + { + var panel = new PanelComponent(" ".Info() + " Dependencies".Main()); + _content = new ContentContainer(); + _content.SetOrientationVertical(); + SetOrientationVertical(); + + _children.Add(panel); + _children.Add(_content); + } + + public void AddChildren(IContainer node) + { + node.SetFixed(Orientation.Vertical, 1); + _content.AddChildren(node); + } + + public override Nodes GetNodes() => _children; + + public Nodes GetContent() => _content.GetNodes(); +} \ No newline at end of file diff --git a/src/TUI/Controls/Containers/DependenciesContainer.cs b/src/TUI/Controls/Containers/DependenciesContainer.cs new file mode 100644 index 0000000..ecf373f --- /dev/null +++ b/src/TUI/Controls/Containers/DependenciesContainer.cs @@ -0,0 +1,105 @@ +using TUI.Controls.Common; +using TUI.Controls.Components; +using TUI.Domain; +using TUI.Engine; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Components; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Controls.Containers; + +public class DependenciesContainer : ContainerBase +{ + public readonly Project? Project; + + private const int VersionColumnWidth = 11; + + private const int TitleColumnWidth = 25; + + private readonly Nodes _dependencies = new(); + + public DependenciesContainer() + { + } + + public DependenciesContainer(Project project) + { + Project = project; + } + + public void AddTitleStub() + { + var size = new Size(TitleColumnWidth, 1); + var title = new StubComponent(size); + title.SetPadding(Level.Normal); + + _dependencies.Add(title); + } + + public void AddTitle(IComponent title) + { + title.SetPadding(Level.Normal); + title.SetFixed(Orientation.Horizontal, TitleColumnWidth); + title.SetAlignment(Horizontal.Left); + + if (Project is not null && Project.Legacy) + { + title.StyleContext = new StyleContext(Palette.DisableColor); + } + + _dependencies.Add(title); + } + + public void AddDependencyStub() + { + var size = new Size(VersionColumnWidth, 1); + var stub = new StubComponent(size, Symbols.NotFound.Hint()); + stub.SetPadding(Level.Normal); + stub.SetAlignment(Horizontal.Right); + stub.SetFixed(Orientation.Horizontal, VersionColumnWidth); + + if (Project is not null && Project.Legacy) + { + stub.StyleContext = new StyleContext(Palette.DisableColor); + } + + _dependencies.Add(stub); + } + + public void AddError() + { + var size = new Size(25, 1); + var stub = new StubComponent(size, (Symbols.Error + Symbols.Space + " Something went wrong").Error()); + stub.SetPadding(Level.Normal); + stub.SetAlignment(Horizontal.Right); + stub.SetFixed(Orientation.Horizontal, 25); + + if (Project is not null && Project.Legacy) + { + stub.StyleContext = new StyleContext(Palette.DisableColor); + } + + _dependencies.Add(stub); + } + + public void AddDependency(Dependency dependency, VersionStatus status = VersionStatus.BeNice) + { + var version = new VersionComponent(dependency.Version, dependency.Brand, status, dependency.Type); + version.SetPadding(Level.Normal); + version.SetAlignment(Horizontal.Right); + version.SetFixed(Orientation.Horizontal, VersionColumnWidth); + + if (Project is not null && Project.Legacy) + { + version.StyleContext = new StyleContext(Palette.DisableColor); + } + + _dependencies.Add(version); + } + + public override Nodes GetNodes() => _dependencies; +} \ No newline at end of file diff --git a/src/TUI/Controls/Containers/FooterContainer.cs b/src/TUI/Controls/Containers/FooterContainer.cs new file mode 100644 index 0000000..132e7fb --- /dev/null +++ b/src/TUI/Controls/Containers/FooterContainer.cs @@ -0,0 +1,32 @@ +using TUI.Controls.Components; +using TUI.Controls.Statics; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Components; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Controls.Containers; + +public class FooterContainer : ContainerBase +{ + private readonly INode _breadcrumbs; + private readonly INode _speaker; + + public FooterContainer(IComponent? breadcrumbs) + { + breadcrumbs.SetAlignment(Horizontal.Left); + breadcrumbs.SetPaddingLeft(Level.Normal); + _breadcrumbs = breadcrumbs; + + _speaker = SpeakerComponent.Instance; + } + + public override Nodes GetNodes() + { + var copyright = new CopyrightComponent(); + copyright.SetAlignment(Horizontal.Right); + copyright.SetPaddingRight(Level.Normal); + return new Nodes { _breadcrumbs, _speaker, copyright }; + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Containers/HeaderContainer.cs b/src/TUI/Controls/Containers/HeaderContainer.cs new file mode 100644 index 0000000..4090a80 --- /dev/null +++ b/src/TUI/Controls/Containers/HeaderContainer.cs @@ -0,0 +1,38 @@ +using TUI.Controls.Statics; +using TUI.Controls.Statics.Hints; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; +using TUI.Engine.Theme; + +namespace TUI.Controls.Containers; + +public class HeaderContainer : ContainerBase, IContainer +{ + public override Nodes GetNodes() + { + var versionHints = new VersionHints(); + versionHints.SetPadding(Indentation.Default); + versionHints.SetAlignment(Horizontal.Left); + + var tagsHints = new TagHints(); + tagsHints.SetPadding(Indentation.Default); + tagsHints.SetAlignment(Horizontal.Left); + + var appTypeHints = new AppTypeHints(); + appTypeHints.SetPadding(Indentation.Default); + appTypeHints.SetAlignment(Horizontal.Left); + + var hotkeysHints = new HotkeysHint(); + hotkeysHints.SetPadding(Indentation.Default); + hotkeysHints.SetAlignment(Horizontal.Left); + + var logo = new LogoComponent(); + logo.SetAlignment(Horizontal.Right); + logo.SetPaddingLeft(Indentation.Default); + logo.SetPaddingBottom(Indentation.Default); + logo.SetPaddingRight(Indentation.Default); + + return new Nodes { versionHints, tagsHints, appTypeHints, hotkeysHints, logo }; + } +} \ 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 0100952..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) - { - 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 69ae5bb..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 void Render(string title, Position position) - { - Console.SetCursorPosition(position.Left, position.Top); - - RenderTopLine(title); - - var marginTop = Theme.BorderWidth + Theme.Padding + position.Top; - var dashboardHeight = Console.WindowHeight - Theme.BorderWidth; - - for (var top = marginTop; top < dashboardHeight; top++) - { - RenderMiddleLine(); - } - - RenderBottomLine(); - } - - private static void RenderMiddleLine() - { - Console.Write("│".Primary()); - Console.Write(new string(' ', Console.WindowWidth - Theme.BorderWidth * 2)); - Console.WriteLine("│".Primary()); - } - - private static void RenderBottomLine() - { - var lineWidth = Console.WindowWidth - Theme.BorderWidth * 2; - Console.Write("└".Primary()); - Console.Write('─'.Repeat(lineWidth).Primary()); - Console.WriteLine("┘".Primary()); - } - - private static void RenderTopLine(string title) - { - 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()); - } -} \ 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 bd819a1..0000000 --- a/src/TUI/Controls/Display.cs +++ /dev/null @@ -1,73 +0,0 @@ -using TUI.Dashboards; -using TUI.Domain; -using TUI.UserInterface; - - -namespace TUI.Controls; - -public class Display -{ - private bool _headerInDisplay = true; - public readonly Header Header; - - public readonly Copyright Copyright; - - public readonly DependencyDashboard DependencyDashboard; - private Project _currentProject; - - public Display() - { - Header = new Header(); - Copyright = new Copyright(); - DependencyDashboard = new DependencyDashboard(); - - Render(); - } - - public void OpenDeps(Project project) - { - _currentProject = project; - var dashboardPosition = new Position(0, Header.Height); - DependencyDashboard.Render(_currentProject, dashboardPosition); - } - - private void ResizeDependencies(bool full) - { - var dashboardPosition = new Position(0, full ? 0 : Header.Height); - 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(!_headerInDisplay); - } - - public void Next() - { - DependencyDashboard.Next(); - } - - public void Previous() - { - DependencyDashboard.Previous(); - } -} \ 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 6fda601..0000000 --- a/src/TUI/Controls/IControl.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace TUI.Controls; - -public interface IControl -{ - void Render(Position position); -} - -public interface IControl -{ - void Render(TProps props, Position position); -} \ No newline at end of file diff --git a/src/TUI/Controls/Layouts/DashboardLayout.cs b/src/TUI/Controls/Layouts/DashboardLayout.cs new file mode 100644 index 0000000..20124f1 --- /dev/null +++ b/src/TUI/Controls/Layouts/DashboardLayout.cs @@ -0,0 +1,32 @@ +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Containers; +using TUI.Engine.Nodes; + +namespace TUI.Controls.Layouts; + +public class DashboardLayout : ContainerBase, IContainer +{ + private readonly INode _header; + private readonly INode _footer; + private readonly INode _dashboard; + + public DashboardLayout(INode header, INode dashboard, INode footer) + { + SetOrientationVertical(); + SetAdaptive(Orientation.Horizontal); + SetAdaptive(Orientation.Vertical); + + header.SetFixed(Orientation.Vertical, 6); + footer.SetFixed(Orientation.Vertical, 1); + + _header = header; + _footer = footer; + _dashboard = dashboard; + } + + public override Nodes GetNodes() => + new() + { + _header, _dashboard, _footer + }; +} \ 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/Statics/CopyrightComponent.cs b/src/TUI/Controls/Statics/CopyrightComponent.cs new file mode 100644 index 0000000..5e6be7f --- /dev/null +++ b/src/TUI/Controls/Statics/CopyrightComponent.cs @@ -0,0 +1,18 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics; + +public class CopyrightComponent : StaticComponentBase +{ + protected override void RenderWithCache(StringBuilder builder) + { + builder.Append(Symbols.Copyright.Info()); + builder.Append(Symbols.Space); + builder.Append("Kolosov A. aka \"dnwSilver\"".Hint()); + builder.Append(Symbols.Space); + builder.Append(DateTime.Now.Year.Info()); + } +} diff --git a/src/TUI/Controls/Statics/Hints/AppTypeHints.cs b/src/TUI/Controls/Statics/Hints/AppTypeHints.cs new file mode 100644 index 0000000..ee432b2 --- /dev/null +++ b/src/TUI/Controls/Statics/Hints/AppTypeHints.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics.Hints; + +public class AppTypeHints : StaticComponentBase +{ + private readonly Dictionary _hints = new() + { + { Symbols.NpmPackage, "package" }, + { Symbols.DockerImage, "image" }, + { Symbols.Site, "site" }, + { Symbols.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/Controls/Statics/Hints/HotkeysHint.cs b/src/TUI/Controls/Statics/Hints/HotkeysHint.cs new file mode 100644 index 0000000..a6ca871 --- /dev/null +++ b/src/TUI/Controls/Statics/Hints/HotkeysHint.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics.Hints; + +public class HotkeysHint : StaticComponentBase +{ + 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/Controls/Statics/Hints/TagHints.cs b/src/TUI/Controls/Statics/Hints/TagHints.cs new file mode 100644 index 0000000..8ee1a95 --- /dev/null +++ b/src/TUI/Controls/Statics/Hints/TagHints.cs @@ -0,0 +1,27 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics.Hints; + +public class TagHints : StaticComponentBase +{ + private readonly Dictionary _hints = new() + { + { Symbols.Auth, "Auth" }, + { Symbols.NetworkPublic, "WWW" }, + { Symbols.Seo, "SEO" }, + { Symbols.Git, "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/Controls/Statics/Hints/VersionHints.cs b/src/TUI/Controls/Statics/Hints/VersionHints.cs new file mode 100644 index 0000000..b271bb2 --- /dev/null +++ b/src/TUI/Controls/Statics/Hints/VersionHints.cs @@ -0,0 +1,28 @@ +using System.Text; +using TUI.Controls.Components; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics.Hints; + +public class VersionHints : StaticComponentBase +{ + private readonly Dictionary _hints = new() + { + { "󰎔", VersionStatus.ToNew.Colorize("too new") }, + { "", VersionStatus.SoGood.Colorize("so good") }, + { "", VersionStatus.BeNice.Colorize("be nice") }, + { "󰬟", VersionStatus.TooOld.Colorize("too old") } + }; + + 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/Controls/Statics/LogoComponent.cs b/src/TUI/Controls/Statics/LogoComponent.cs new file mode 100644 index 0000000..6f58008 --- /dev/null +++ b/src/TUI/Controls/Statics/LogoComponent.cs @@ -0,0 +1,20 @@ +using System.Text; +using TUI.Engine; +using TUI.Engine.Components; +using TUI.Engine.Theme; + +namespace TUI.Controls.Statics; + +public class LogoComponent : StaticComponentBase +{ + 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/Controls/Table.cs b/src/TUI/Controls/Table.cs deleted file mode 100644 index 353ca3e..0000000 --- a/src/TUI/Controls/Table.cs +++ /dev/null @@ -1,74 +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 Position _position; - private int _selectedRowId; - - public void Render(TableProps props, Position position) - { - _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); - } - } - - private readonly Dictionary _rows = new(); - - 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 77076b1..0000000 --- a/src/TUI/Dashboards/DependencyDashboard.cs +++ /dev/null @@ -1,215 +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 Table _table = new(); - - public void Render(Project project, Position position) - { - var dashboard = new Dashboard(); - dashboard.Render(project.Icon, 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 readonly static Dictionary Packages = new(); - - private static Package DownloadPackage(SourceDto sourceDto) - { - if (Packages.TryGetValue(sourceDto.Repo, out var downloadPackage)) - { - return downloadPackage; - } - - using HttpClient client = new(); - var endpoint = sourceDto.Tags.Have("gitlab") ? GetGitlabEndpoint(sourceDto) : sourceDto.Repo; - 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=master"; - } - - 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) => 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/Brand.cs b/src/TUI/Domain/Brand.cs new file mode 100644 index 0000000..f3422a6 --- /dev/null +++ b/src/TUI/Domain/Brand.cs @@ -0,0 +1,8 @@ +using Pastel; + +namespace TUI.Domain; + +public record Brand(string Name, string? Logo = null, string? Color = null) +{ + public string ColorLogo() => Logo?.Pastel(Color) ?? string.Empty; +}; \ No newline at end of file diff --git a/src/TUI/Domain/Dependency.cs b/src/TUI/Domain/Dependency.cs new file mode 100644 index 0000000..b29419a --- /dev/null +++ b/src/TUI/Domain/Dependency.cs @@ -0,0 +1,67 @@ +using TUI.Controls.Components; + +namespace TUI.Domain; + +public record Dependency() +{ + private readonly Version _current; + public readonly Brand Brand; + public VersionType Type { get; private set; } + + public string Version => $"{_current.Major}.{_current.Minor}.{_current.Patch}"; + + public Dependency(string version, Brand brand) : this() + { + _current = new Version(version); + Brand = brand; + } + + public VersionStatus Comparison(Dependency outerDependency) + { + // if (string.IsNullOrEmpty(Version) || string.IsNullOrEmpty(outerDependency.Version)) + // { + // return VersionStatus.NotFound; + // } + + var outer = outerDependency._current; + + Type = _current.Type; + + if (_current.Major < outer.Major) + { + return VersionStatus.TooOld; + } + + if (_current.Major > outer.Major) + { + return VersionStatus.ToNew; + } + + if (_current.Minor < outer.Minor) + { + return VersionStatus.BeNice; + } + + if (_current.Minor > outer.Minor) + { + return VersionStatus.ToNew; + } + + if (_current.Patch < outer.Patch) + { + return VersionStatus.BeNice; + } + + if (_current.Patch > outer.Patch) + { + return VersionStatus.ToNew; + } + + if (outer.Type != VersionType.Release) + { + return VersionStatus.ToNew; + } + + return VersionStatus.SoGood; + } +}; \ No newline at end of file diff --git a/src/TUI/Domain/Hub.cs b/src/TUI/Domain/Hub.cs new file mode 100644 index 0000000..403d320 --- /dev/null +++ b/src/TUI/Domain/Hub.cs @@ -0,0 +1,3 @@ +namespace TUI.Domain; + +public record Hub(string Origin, string Type); \ No newline at end of file diff --git a/src/TUI/Domain/Project.cs b/src/TUI/Domain/Project.cs index dc08f5b..c75be9f 100644 --- a/src/TUI/Domain/Project.cs +++ b/src/TUI/Domain/Project.cs @@ -1,21 +1,14 @@ -using TUI.Settings; -using YamlDotNet.Serialization; - - namespace TUI.Domain; -[YamlSerializable] -public class Project +public record Project(int Id, string Name, IEnumerable Tags, Hub Hub) { - [YamlMember] - public string Icon { get; set; } - - [YamlMember] - public string Name { get; set; } - - [YamlMember] - public DependencyDto[] Dependencies { get; set; } - - [YamlMember] - public IList Sources { get; set; } + private IEnumerable Dependencies => new List(); + + public bool IsPublicNetwork => Tags.Contains("public"); + + public bool HasAuth => Tags.Contains("auth"); + + public bool SeoDependent => Tags.Contains("seo"); + + public bool Legacy => Tags.Contains("legacy"); } \ No newline at end of file diff --git a/src/TUI/Domain/Settings.cs b/src/TUI/Domain/Settings.cs deleted file mode 100644 index 0a4c676..0000000 --- a/src/TUI/Domain/Settings.cs +++ /dev/null @@ -1,22 +0,0 @@ -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - - -namespace TUI.Domain; - -[YamlSerializable] -public class Settings -{ - public static Settings Init() - { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); - - using var sr = new StreamReader("settings.yaml"); - return deserializer.Deserialize(sr.ReadToEnd()); - } - - [YamlMember] - public Project[] Projects { get; set; } -} \ No newline at end of file diff --git a/src/TUI/Domain/Version.cs b/src/TUI/Domain/Version.cs new file mode 100644 index 0000000..ed61736 --- /dev/null +++ b/src/TUI/Domain/Version.cs @@ -0,0 +1,32 @@ +using TUI.Controls.Components; + +namespace TUI.Domain; + +public class Version +{ + public readonly int Major; + public readonly int Minor; + public readonly int Patch; + public readonly VersionType Type; + + public Version(string version) + { + var parts = version.Split('.'); + + Major = Convert.ToInt32(parts[0].Replace("^", "").Replace("~", "")); + Minor = Convert.ToInt32(parts[1]); + Patch = Convert.ToInt32(string.Join("", parts[2].TakeWhile(char.IsDigit))); + + var extension = parts[2].Replace(Patch.ToString(), ""); + + Type = extension switch + { + not null when extension.Contains("rc") => VersionType.Candidate, + not null when extension.Contains("beta") => VersionType.Beta, + not null when extension.Contains("alpha") => VersionType.Alpha, + not null when extension.Contains("canary") => VersionType.Canary, + not null when extension.Contains("next") => VersionType.Next, + _ => VersionType.Release + }; + } +} \ No newline at end of file diff --git a/src/TUI/Extensions.cs b/src/TUI/Extensions.cs deleted file mode 100644 index 310da13..0000000 --- a/src/TUI/Extensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Text.RegularExpressions; - - -namespace TUI; - -public static class Extensions -{ - public static bool Have(this IEnumerable array, string findValue) - { - return array.Any(item => item == findValue); - } - - public static string Repeat(this char symbol, int repeatCount) - { - return repeatCount < 0 ? "" : new string(symbol, repeatCount); - } - - public static string RemoveColors(this string text) - { - return Regex.Replace(text, @"\S\[(\d{0,3}[;m][_]?){0,5}", ""); - } - - public static int Width(this string text) - { - if (string.IsNullOrEmpty(text)) - { - return 0; - } - - var clearText = text.RemoveColors(); - var stringInfo = new System.Globalization.StringInfo(clearText); - return stringInfo.LengthInTextElements; - } - - public static Version? ToVersion(this string textVersion) - { - var version = textVersion.Replace("^", "").Replace("~", "").Split("."); - if (version.Length != 3) - return null; - var major = Convert.ToInt32(version[0]); - var minor = Convert.ToInt32(version[1]); - var patch = Convert.ToInt32(version[2].Split('-')[0]); - return new Version(major, minor, patch); - } -} \ No newline at end of file diff --git a/src/TUI/Pages/DependenciesPage.cs b/src/TUI/Pages/DependenciesPage.cs new file mode 100644 index 0000000..71b84f6 --- /dev/null +++ b/src/TUI/Pages/DependenciesPage.cs @@ -0,0 +1,178 @@ +using TUI.Controls.Components; +using TUI.Controls.Containers; +using TUI.Controls.Layouts; +using TUI.Engine.Rendering.Canvas; +using TUI.Store; + +namespace TUI.Pages; + +public record DependenciesState(HeaderContainer Header, DashboardContainer Dashboard, FooterContainer Footer); + +public class DependenciesPage : PageBase +{ + private DependenciesPage() + { + } + + public static DependenciesPage Instance { get; } = new(); + + private DependenciesStore _store; + + private DependenciesState _state; + + public override void Initial() + { + var header = new HeaderContainer(); + var dashboard = new DashboardContainer(); + var dependenciesHeader = new DependenciesContainer(); + dependenciesHeader.AddTitleStub(); + + foreach (var conventionDependency in _store.ConventionDependencies) + { + dependenciesHeader.AddDependency(conventionDependency); + } + + dashboard.AddChildren(dependenciesHeader); + + foreach (var project in _store.Projects) + { + var projectDependencies = new DependenciesContainer(project); + projectDependencies.AddTitle(new ProjectTitle(project)); + dashboard.AddChildren(projectDependencies); + } + + var breadCrumbs = new BreadCrumbsComponent(" Dependencies", "JavaScript"); + var footer = new FooterContainer(breadCrumbs); + + _state = new DependenciesState(header, dashboard, footer); + } + + public override void Render() + { + ICanvas canvas = new ConsoleCanvas(); + var layout = new DashboardLayout(_state.Header, _state.Dashboard, _state.Footer); + canvas.Draw(layout); + } + + public override void Load() + { + Initial(); + var projects = _state.Dashboard.GetContent(); + foreach (var projectDependencies in projects.Cast().Skip(1)) + { + if (projectDependencies is null) + { + continue; + } + + var project = projectDependencies.Project; + var actualDependencies = _store.ActualDependencies(project).ToArray(); + + if (!actualDependencies.Any()) + { + projectDependencies.AddError(); + } + else + { + foreach (var conventionDependency in _store.ConventionDependencies) + { + var actualDependency = actualDependencies.SingleOrDefault( + dependency => string.Equals(dependency.Brand.Name, conventionDependency.Brand.Name, + StringComparison.CurrentCultureIgnoreCase)); + + if (actualDependency is null) + { + projectDependencies.AddDependencyStub(); + continue; + } + + var versionType = actualDependency.Comparison(conventionDependency); + projectDependencies.AddDependency(actualDependency, versionType); + } + } + + Render(); + } + } + + public override void Bind() + { + _store = new DependenciesStore(); + _store.Bind(); + } + + // 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/Pages/IPage.cs b/src/TUI/Pages/IPage.cs new file mode 100644 index 0000000..61028ef --- /dev/null +++ b/src/TUI/Pages/IPage.cs @@ -0,0 +1,10 @@ +namespace TUI.Pages; + +interface IPage +{ + void Open(); + void Initial(); + void Render(); + void Bind(); + void Load(); +} \ No newline at end of file diff --git a/src/TUI/Pages/PageBase.cs b/src/TUI/Pages/PageBase.cs new file mode 100644 index 0000000..91aa0e7 --- /dev/null +++ b/src/TUI/Pages/PageBase.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; + +namespace TUI.Pages; + +public abstract class PageBase : IPage +{ + public void Open() + { + Debugger.Log(0, "Event", $"Open page ${GetType().UnderlyingSystemType.Name}\n"); + Bind(); + Initial(); + Render(); + } + + public abstract void Load(); + + public abstract void Initial(); + + public abstract void Render(); + + public abstract void Bind(); +} \ No newline at end of file diff --git a/src/TUI/Pages/WelcomePage.cs b/src/TUI/Pages/WelcomePage.cs new file mode 100644 index 0000000..a556b65 --- /dev/null +++ b/src/TUI/Pages/WelcomePage.cs @@ -0,0 +1,47 @@ +using TUI.Controls.Common; +using TUI.Controls.Components; +using TUI.Controls.Containers; +using TUI.Controls.Layouts; +using TUI.Controls.Statics; +using TUI.Engine.Attributes; +using TUI.Engine.Rendering.Canvas; + +namespace TUI.Pages; + +public class WelcomePage : PageBase +{ + private WelcomePage() + { + } + + public static WelcomePage Instance { get; } = new(); + + public override void Initial() + { + } + + public override void Render() + { + ICanvas canvas = new ConsoleCanvas(); + + var header = new StubComponent(new Size(1, 1)); + + var logo = new LogoComponent(); + + var breadCrumbs = new BreadCrumbsComponent(); + + var footer = new FooterContainer(breadCrumbs); + + var layout = new DashboardLayout(header, logo, footer); + + canvas.Draw(layout); + } + + public override void Load() + { + } + + public override void Bind() + { + } +} \ No newline at end of file diff --git a/src/TUI/Program.cs b/src/TUI/Program.cs index ed56ff6..c5120cb 100644 --- a/src/TUI/Program.cs +++ b/src/TUI/Program.cs @@ -1,32 +1,48 @@ -using TUI.Controls; -using Settings = TUI.Domain.Settings; +using TUI.Pages; Console.Clear(); Console.CursorVisible = false; -var settings = Settings.Init(); +var welcomePage = WelcomePage.Instance; +welcomePage.Open(); +Thread.Sleep(500); -var display = new Display(); -display.OpenDeps(settings.Projects[0]); +IPage currentPage = DependenciesPage.Instance; +currentPage.Open(); -var hotKey = ConsoleKey.NoName; + +ConsoleKeyInfo? key = null; + +var waitCommand = true; do { - switch (hotKey) + switch (key?.Key) { - case ConsoleKey.DownArrow: - display.Next(); + case ConsoleKey.Q: + waitCommand = false; break; - case ConsoleKey.UpArrow: - display.Previous(); + case ConsoleKey.R: + key = null; + currentPage.Load(); break; - case ConsoleKey.E: - display.ToggleHeader(); + case ConsoleKey.D1: + key = null; + currentPage = DependenciesPage.Instance; + Console.Clear(); + currentPage.Render(); + break; + case ConsoleKey.D0: + key = null; + currentPage = WelcomePage.Instance; + Console.Clear(); + currentPage.Render(); + break; + default: + key = Console.ReadKey(true); break; } +} while (waitCommand); - hotKey = Console.ReadKey(intercept: true).Key; -} while (hotKey != ConsoleKey.Q); - -Console.Clear(); \ No newline at end of file +Console.Clear(); +Console.CursorVisible = true; \ No newline at end of file diff --git a/src/TUI/Providers/Dependencies/DependenciesDto.cs b/src/TUI/Providers/Dependencies/DependenciesDto.cs new file mode 100644 index 0000000..f42b152 --- /dev/null +++ b/src/TUI/Providers/Dependencies/DependenciesDto.cs @@ -0,0 +1,11 @@ +using System.Runtime.Serialization; +using YamlDotNet.Serialization; + +namespace TUI.Providers.Dependencies; + +[DataContract] +[YamlSerializable] +public class DependenciesDto +{ + [DataMember] [YamlMember] public StackDto[] Stacks { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Settings/DependencyDto.cs b/src/TUI/Providers/Dependencies/DependencyDto.cs similarity index 92% rename from src/TUI/Settings/DependencyDto.cs rename to src/TUI/Providers/Dependencies/DependencyDto.cs index 53d770c..10c0715 100644 --- a/src/TUI/Settings/DependencyDto.cs +++ b/src/TUI/Providers/Dependencies/DependencyDto.cs @@ -1,8 +1,7 @@ using System.Runtime.Serialization; using YamlDotNet.Serialization; - -namespace TUI.Settings; +namespace TUI.Providers.Dependencies; [DataContract] [YamlSerializable] @@ -13,7 +12,7 @@ public class DependencyDto [DataMember] [YamlMember] public string? Name { get; set; } - + [DataMember] [YamlMember] public string? Icon diff --git a/src/TUI/Providers/Dependencies/DependencyRepository.cs b/src/TUI/Providers/Dependencies/DependencyRepository.cs new file mode 100644 index 0000000..45ac9d9 --- /dev/null +++ b/src/TUI/Providers/Dependencies/DependencyRepository.cs @@ -0,0 +1,126 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using TUI.Domain; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace TUI.Providers.Dependencies; + +public class DependencyRepository +{ + private DependenciesDto? _dependenciesDto; + + private DependenciesDto DependenciesDto + { + get + { + if (_dependenciesDto is not null) + { + return _dependenciesDto; + } + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + + using var sr = new StreamReader("dependencies.yaml"); + _dependenciesDto = deserializer.Deserialize(sr.ReadToEnd()); + + return _dependenciesDto; + } + } + + public IEnumerable ReadConventions(string stackName) + { + return DependenciesDto.Stacks + .Single(stack => stack.Name == stackName) + .Conventions + .Select(convention => + { + var brand = new Brand(convention.Name, convention.Icon, convention.Color); + return new Dependency(convention.Version, brand); + }); + } + + public IEnumerable ReadProjects(string stackName) + { + var projects = new List(); + + var hubs = DependenciesDto.Stacks + .Single(stack => stack.Name == stackName) + .Hubs; + + foreach (var hub in hubs) + { + projects.AddRange(hub + .Projects + .Select(proj => new Project(proj.Id, proj.Name, proj.Tags, new Hub(hub.Origin, hub.Type)))); + } + + return projects; + } + + + public IEnumerable ReadActual(Project project) + { + var dependencies = new List(); + + if (project.Hub.Type == "gitlab") + { + var endpoint = GetGitlabEndpoint(project.Hub.Origin, project.Id); + using HttpClient client = new(); + var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult(); + + var packageJson = JsonSerializer.Deserialize(json); + + dependencies.AddRange(Map(packageJson?.Dependencies)); + dependencies.AddRange(Map(packageJson?.DevDependencies)); + dependencies.AddRange(Map(packageJson?.Engines)); + } + + return dependencies; + } + + private static string GetGitlabEndpoint(string origin, int projectId) + { + var token = Environment.GetEnvironmentVariable("TLD_GITLAB_PAT"); + return $"{origin}/api/v4/projects/{projectId}/repository/files/package.json/raw?" + + $"private_token={token}&ref=dev"; + } + + private static IEnumerable Map(JsonObject? dependencies) + { + if (dependencies is null) + { + yield break; + } + + foreach (var dependency in dependencies) + { + var actualVersion = dependency.Value?.ToString(); + var brand = new Brand(dependency.Key); + + if (actualVersion is null) + { + continue; + } + + yield return new Dependency(actualVersion, brand); + } + } +} + + +// private static Package DownloadPackage(ProjectDto project) +// { +// // var endpoint = projectDto.Tags.Have("gitlab") ? GetGitlabEndpoint(projectDto) : projectDto.Repo; +// var endpoint = ""; +// if (Packages.TryGetValue(endpoint, out var downloadPackage)) return downloadPackage; +// +// using HttpClient client = new(); +// var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult(); +// +// Packages.Add(endpoint, package); +// return package; +// } +// \ No newline at end of file diff --git a/src/TUI/Providers/Dependencies/HubDto.cs b/src/TUI/Providers/Dependencies/HubDto.cs new file mode 100644 index 0000000..ebb225f --- /dev/null +++ b/src/TUI/Providers/Dependencies/HubDto.cs @@ -0,0 +1,15 @@ +using System.Runtime.Serialization; +using YamlDotNet.Serialization; + +namespace TUI.Providers.Dependencies; + +[DataContract] +[YamlSerializable] +public class HubDto +{ + [YamlMember] [DataMember] public string Origin { get; set; } + + [YamlMember] [DataMember] public string Type { get; set; } + + [YamlMember] [DataMember] public IEnumerable Projects { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Domain/Package.cs b/src/TUI/Providers/Dependencies/PackageJson.cs similarity index 62% rename from src/TUI/Domain/Package.cs rename to src/TUI/Providers/Dependencies/PackageJson.cs index b7f7f87..30387aa 100644 --- a/src/TUI/Domain/Package.cs +++ b/src/TUI/Providers/Dependencies/PackageJson.cs @@ -1,10 +1,10 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using TUI.Engine; +namespace TUI.Providers.Dependencies; -namespace TUI.Domain; - -public class Package +public class PackageJson { [JsonPropertyName("dependencies")] public JsonObject? Dependencies { get; set; } @@ -14,29 +14,20 @@ public class Package [JsonPropertyName("engines")] public JsonObject? Engines { get; set; } - + public Version? ParseVersion(string? dependencyName) { - if (dependencyName == null) - { - return null; - } - + if (dependencyName == null) return null; + JsonNode? version = null; - + var lowerDependencyName = dependencyName.ToLower(); Dependencies?.TryGetPropertyValue(lowerDependencyName, out version); - if (version == null) - { - Engines?.TryGetPropertyValue(lowerDependencyName, out version); - } - - if (version == null) - { - DevDependencies?.TryGetPropertyValue(lowerDependencyName, out version); - } - + if (version == null) Engines?.TryGetPropertyValue(lowerDependencyName, out version); + + if (version == null) DevDependencies?.TryGetPropertyValue(lowerDependencyName, out version); + return version?.GetValue().ToVersion(); } } \ No newline at end of file diff --git a/src/TUI/Providers/Dependencies/ProjectDto.cs b/src/TUI/Providers/Dependencies/ProjectDto.cs new file mode 100644 index 0000000..72066e5 --- /dev/null +++ b/src/TUI/Providers/Dependencies/ProjectDto.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; +using YamlDotNet.Serialization; + +namespace TUI.Providers.Dependencies; + +[YamlSerializable] +public class ProjectDto +{ + [DataMember] [YamlMember] public int Id { get; set; } + + [DataMember] [YamlMember] public string Name { get; set; } + + [DataMember] [YamlMember] public string Deps { get; set; } + + [DataMember] [YamlMember] public IEnumerable Tags { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Providers/Dependencies/StackDto.cs b/src/TUI/Providers/Dependencies/StackDto.cs new file mode 100644 index 0000000..9c10cc6 --- /dev/null +++ b/src/TUI/Providers/Dependencies/StackDto.cs @@ -0,0 +1,17 @@ +using System.Runtime.Serialization; +using YamlDotNet.Serialization; + +namespace TUI.Providers.Dependencies; + +[DataContract] +[YamlSerializable] +public class StackDto +{ + [YamlMember] [DataMember] public string Name { get; set; } + + [YamlMember] [DataMember] public string Icon { get; set; } + + [YamlMember] [DataMember] public DependencyDto[] Conventions { get; set; } + + [YamlMember] [DataMember] public IEnumerable Hubs { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Settings/SourceDto.cs b/src/TUI/Settings/SourceDto.cs deleted file mode 100644 index 100db53..0000000 --- a/src/TUI/Settings/SourceDto.cs +++ /dev/null @@ -1,20 +0,0 @@ -using YamlDotNet.Serialization; - - -namespace TUI.Settings; - -[YamlSerializable] -public class SourceDto -{ - [YamlMember] - public string[] Tags { get; set; } - - [YamlMember] - public string Name { get; set; } - - [YamlMember] - public int ProjectId { get; set; } = 0; - - [YamlMember] - public string Repo { get; set; } -} \ No newline at end of file diff --git a/src/TUI/Store/DependenciesStore.cs b/src/TUI/Store/DependenciesStore.cs new file mode 100644 index 0000000..9d8c9d4 --- /dev/null +++ b/src/TUI/Store/DependenciesStore.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; +using TUI.Controls.Components; +using TUI.Domain; +using TUI.Engine; +using TUI.Engine.Theme; +using TUI.Providers.Dependencies; + +namespace TUI.Store; + +public class DependenciesStore +{ + public IEnumerable ConventionDependencies; + + public IEnumerable Projects; + + private DependencyRepository Repository = new(); + + public IEnumerable ActualDependencies(Project project) + { + SpeakerComponent.Instance.Shout(Symbols.Download.Info(), + $"Fetch actual dependencies for project {project.Name.Main()}"); + + try + { + return Repository.ReadActual(project); + } + catch(Exception ex) + { + Debugger.Log(0, "error", ex.Message); + SpeakerComponent.Instance.Shout(Symbols.Error.Error(), $"Fetch failed for project{project.Name}"); + return new List(); + } + } + + public void Bind() + { + SpeakerComponent.Instance.Shout("🤔", "Prepare javascript conventions"); + ConventionDependencies = Repository.ReadConventions("javascript"); + + SpeakerComponent.Instance.Shout("🤩", "Prepare javascript projects"); + Projects = Repository.ReadProjects("javascript"); + } +} \ No newline at end of file diff --git a/src/TUI/TUI.csproj b/src/TUI/TUI.csproj index 05b1ebc..8cf2073 100644 --- a/src/TUI/TUI.csproj +++ b/src/TUI/TUI.csproj @@ -1,22 +1,34 @@ - - Exe - net7.0 - enable - enable - 0.1.0 - + + Exe + net7.0 + enable + enable + 0.1.0 + - - - - + + + + - - - Always - - + + + Always + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + <_Parameter1>$(MSBuildProjectName).Controls.Tests + + + + + diff --git a/src/TUI/UserInterface/CommandLine.cs b/src/TUI/UserInterface/CommandLine.cs new file mode 100644 index 0000000..a4cd92a --- /dev/null +++ b/src/TUI/UserInterface/CommandLine.cs @@ -0,0 +1,19 @@ +// 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 39bebf7..0000000 --- a/src/TUI/UserInterface/Header.cs +++ /dev/null @@ -1,82 +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 _hotKeys = new() - { - { "", "select prev" }, - { "", "select next" }, - { "󰬌", "toggle head" }, - { "󰬘", "quit" }, - }; - - private readonly Dictionary _hints = new() - { - { "󰎔", "too new".Info() }, - { "", "so good" }, - { "", "be nice".Primary() }, - { "󰬟", "too old".Warning() } - }; - - 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 2e83dfb..07e7509 100644 --- a/src/TUI/UserInterface/Icons.cs +++ b/src/TUI/UserInterface/Icons.cs @@ -1,32 +1,15 @@ -using Pastel; +using TUI.Engine; namespace TUI.UserInterface; public static class Icons { - 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 DockerImage => GetIcon("󰡨", "086DD7"); - public static string NpmPackage => GetIcon("", "CB0000"); - public static string SEO => GetIcon("󰚩", "4285F4"); - public static string Auth => GetIcon("", "FFD700"); - public static string NotFound => GetIcon(""); - - public readonly static Dictionary Applications = new() + public static readonly Dictionary Applications = new() { - { NpmPackage, "package" }, - { DockerImage, "image" }, - { Site, "site" }, - { Api, "api" }, + { Symbols.NpmPackage.Colorized(), "package" }, + { Symbols.DockerImage.Colorized(), "image" }, + { Symbols.Site.Colorized(), "site" }, + { Symbols.Api, "api" } }; - - private static string GetIcon(string icon, string? activeColor = null) => - icon.Pastel(activeColor ?? Palette.HintColor); } \ No newline at end of file diff --git a/src/TUI/UserInterface/Panel.cs b/src/TUI/UserInterface/Panel.cs index be70b0a..0054752 100644 --- a/src/TUI/UserInterface/Panel.cs +++ b/src/TUI/UserInterface/Panel.cs @@ -1,4 +1,5 @@ -using TUI.Settings; +using TUI.Engine.Theme; +using TUI.Providers.Dependencies; namespace TUI.UserInterface; @@ -11,13 +12,16 @@ public static class Panel private const int TagCount = 5; private const int TagWidth = 2; - public static void RenderRows(SourceDto[] sources, int selectedRowNumber) + + private static int _marginTop; + + public static void RenderRows(ProjectDto[] sources, int selectedRowNumber) { 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) { @@ -28,20 +32,18 @@ public static class Panel } for (var index = 0; index < sources.Length; index++) - { Console.SetCursorPosition(TitleWidth, - 6 + index + _marginTop + BorderWidth + Theme.Padding); - // var source = sources[index]; - // var package = DownloadPackage(source); - // var resultText = package.Dependencies.React; - // resultText = new string(' ', ColumnWidth - resultText.Width()) + resultText; - // if (selectedRowNumber == index + 1) - // { - // resultText = resultText.PastelBg("292928"); - // } - // - // Console.Write(resultText); - } + 6 + index + _marginTop + BorderWidth + Convert.ToInt32(Indentation.Default)); + // var source = sources[index]; + // var package = DownloadPackage(source); + // var resultText = package.Dependencies.React; + // resultText = new string(' ', ColumnWidth - resultText.Width()) + resultText; + // if (selectedRowNumber == index + 1) + // { + // resultText = resultText.PastelBg("292928"); + // } + // + // Console.Write(resultText); // for (var index = 0; index < sources.Length; index++) // { // var loading = true; @@ -74,9 +76,6 @@ public static class Panel // } } - - private static int _marginTop; - // private static Package DownloadPackage(Source source) // { // if (Packages.TryGetValue(source.Repo, out var downloadPackage)) 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/TUI.Controls.Tests/TUI.Controls.Tests.csproj b/tests/TUI.Controls.Tests/TUI.Controls.Tests.csproj new file mode 100644 index 0000000..de51f70 --- /dev/null +++ b/tests/TUI.Controls.Tests/TUI.Controls.Tests.csproj @@ -0,0 +1,33 @@ + + + + net7.0 + enable + enable + + false + true + TUI.Engine.Tests + TUI.Engine.Tests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/TUI.Controls.Tests/Usings.cs b/tests/TUI.Controls.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/TUI.Controls.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/TUI.Controls.Tests/VersionComponentTests.cs b/tests/TUI.Controls.Tests/VersionComponentTests.cs new file mode 100644 index 0000000..e07d621 --- /dev/null +++ b/tests/TUI.Controls.Tests/VersionComponentTests.cs @@ -0,0 +1,54 @@ +using FluentAssertions; +using TUI.Controls.Components; +using TUI.Domain; +using TUI.Engine.Attributes; +using TUI.Engine.Components; + +namespace TUI.Engine.Tests +{ + public class VersionComponentTests + { + [Theory] + [Trait("Category", nameof(Sketch))] + [InlineData(VersionStatus.BeNice, "\u001b[38;2;132;186;100m10.12.33\u001b[0m")] + [InlineData(VersionStatus.SoGood, "\u001b[38;2;113;121;126m10.12.33\u001b[0m")] + [InlineData(VersionStatus.ToNew, "\u001b[38;2;37;121;159m10.12.33\u001b[0m")] + [InlineData(VersionStatus.TooOld, "\u001b[38;2;236;151;6m10.12.33\u001b[0m")] + public void DrawSketchVersionTypes(VersionStatus versionStatus, string expected) + { + var brand = new Brand("Docker", "󰡨", "#1d63ed"); + var version = new VersionComponent("10.12.33", brand, versionStatus); + + var sketch = (version as IComponent).MakeSketch(new Size(10, 2)); + + sketch.ToString().Should().Be(expected); + } + + [Theory] + [Trait("Category", nameof(Dependency))] + [InlineData("1.0.0", "0.0.1", VersionStatus.ToNew)] + [InlineData("1.0.0", "0.1.1", VersionStatus.ToNew)] + [InlineData("1.0.0", "0.1.0", VersionStatus.ToNew)] + [InlineData("1.2.0", "1.0.0", VersionStatus.ToNew)] + [InlineData("1.2.0", "1.0.1", VersionStatus.ToNew)] + [InlineData("1.2.0", "1.1.0", VersionStatus.ToNew)] + [InlineData("1.0.0", "1.0.0-rc", VersionStatus.ToNew)] + [InlineData("1.0.0", "1.0.0", VersionStatus.SoGood)] + [InlineData("^1.0.0", "1.0.0", VersionStatus.SoGood)] + [InlineData("1.2.0", "1.3.0", VersionStatus.BeNice)] + [InlineData("1.3.1", "1.3.3", VersionStatus.BeNice)] + [InlineData("1.2.0", "2.1.0", VersionStatus.TooOld)] + [InlineData("1.2.0", "2.0.1", VersionStatus.TooOld)] + [InlineData("1.2.0", "2.3.1", VersionStatus.TooOld)] + public void ComparisonDependencies(string actual, string convention, VersionStatus expectedType) + { + var brand = new Brand("Poker", "󱢢", "#1d63ed"); + var actualDependency = new Dependency(actual, brand); + var conventionDependency = new Dependency(convention, brand); + + var status = actualDependency.Comparison(conventionDependency); + + status.Should().Be(expectedType); + } + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Components/ComponentBaseTests.cs b/tests/TUI.Engine.Tests/Components/ComponentBaseTests.cs new file mode 100644 index 0000000..5f2c32a --- /dev/null +++ b/tests/TUI.Engine.Tests/Components/ComponentBaseTests.cs @@ -0,0 +1,69 @@ +using FluentAssertions; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Components; +using TUI.Engine.Tests.Stubs; +using TUI.Engine.Theme; + +namespace TUI.Engine.Tests.Components; + +public class ComponentBaseTests +{ + [Fact] + [Trait("Category", nameof(IComponent))] + public void WhenUseChainingSaveAllChange() + { + var logo = new TestComponent(); + logo.SetPadding(Level.Normal); + logo.SetAlignment(Vertical.Center); + logo.SetAlignment(Horizontal.Center); + + logo.Padding.Top.Should().Be(Level.Normal); + logo.Padding.Left.Should().Be(Level.Normal); + logo.Padding.Bottom.Should().Be(Level.Normal); + logo.Padding.Right.Should().Be(Level.Normal); + logo.Alignment.Horizontal.Should().Be(Horizontal.Center); + logo.Alignment.Vertical.Should().Be(Vertical.Center); + } + + [Fact] + [Trait("Category", nameof(IComponent))] + public void WhenSetPaddingsSaveAllChange() + { + var component = new TestComponent(); + + component.SetPadding(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] + [Trait("Category", nameof(IComponent))] + [InlineData(Vertical.Bottom)] + [InlineData(Vertical.Center)] + [InlineData(Vertical.Top)] + public void WhenSetVerticalAlignSaveAllChange(Vertical alignment) + { + var component = new TestComponent(); + + component.SetAlignment(alignment); + + component.Alignment.Vertical.Should().Be(alignment); + } + + [Theory] + [Trait("Category", nameof(IComponent))] + [InlineData(Horizontal.Left)] + [InlineData(Horizontal.Center)] + [InlineData(Horizontal.Right)] + public void WhenSetHorizontalAlignSaveAllChange(Horizontal alignment) + { + var component = new TestComponent(); + + component.SetAlignment(alignment); + + component.Alignment.Horizontal.Should().Be(alignment); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/ComponentBaseTests.cs b/tests/TUI.Engine.Tests/Draw/ComponentBaseTests.cs new file mode 100644 index 0000000..7a5dd07 --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/ComponentBaseTests.cs @@ -0,0 +1,18 @@ +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; +using TUI.Engine.Tests.Stubs; + +namespace TUI.Engine.Tests.Draw; + +public class ComponentBaseTests +{ + protected readonly TestComponent Component = Prepare.Component(); + + protected IDrawable Craftsman(ICanvas canvas) + { + var componentCraftsman = new ComponentCraftsman(canvas); + var containerCraftsman = new ContainerCraftsman(componentCraftsman); + return new DrawCraftsman(componentCraftsman, containerCraftsman); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/DrawAlignmentTests.cs b/tests/TUI.Engine.Tests/Draw/DrawAlignmentTests.cs new file mode 100644 index 0000000..32354c7 --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/DrawAlignmentTests.cs @@ -0,0 +1,89 @@ +using Moq; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; + +namespace TUI.Engine.Tests.Draw; + +public class DrawAlignmentTests : ComponentBaseTests +{ + [Theory] + [Trait("Category", nameof(IDrawable.Draw))] + [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 DrawWithHorizontalAlignment(Horizontal alignment, string content, int canvasSize, + int expectedPosition) + { + var canvas = Mock.Of(w => w.Size == new Size(canvasSize, canvasSize)); + Component.SetContent(content); + Component.SetAlignment(Vertical.Top); + Component.SetAlignment(alignment); + + Craftsman(canvas).Draw(Component, Position.Default, canvas.Size); + + Mock.Get(canvas).Verify(w => w.Paint(content), Times.Once()); + Mock.Get(canvas).Verify(w => w.SetPencil(new Position(expectedPosition, 0)), Times.Once()); + } + + [Theory] + [Trait("Category", nameof(IDrawable.Draw))] + [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 DrawWithVerticalAlignment(Vertical alignment, string content, int canvasSize, int[] expectedPositions) + { + var canvas = Mock.Of(w => w.Size == new Size(canvasSize, canvasSize)); + Component.SetContent(content); + Component.SetAlignment(Horizontal.Left); + Component.SetAlignment(alignment); + + Craftsman(canvas).Draw(Component, Position.Default, canvas.Size); + + foreach (var expectedPencilPosition in expectedPositions) + { + Mock.Get(canvas).VerifyPositionOnce(0, expectedPencilPosition); + } + } + + [Theory] + [Trait("Category", nameof(IDrawable.Draw))] + [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 DrawWithAlignment(Horizontal horizontal, Vertical vertical, int expectedLeft, + int expectedTop) + { + var canvas = Mock.Of(w => w.Size == new Size(6, 5)); + Component.SetContent("VV"); + Component.SetAlignment(horizontal); + Component.SetAlignment(vertical); + + Craftsman(canvas).Draw(Component, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(expectedLeft, expectedTop); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/DrawOverloadTests.cs b/tests/TUI.Engine.Tests/Draw/DrawOverloadTests.cs new file mode 100644 index 0000000..6ea8d0f --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/DrawOverloadTests.cs @@ -0,0 +1,46 @@ +using Moq; +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; +using TUI.Engine.Tests.Stubs; + +namespace TUI.Engine.Tests.Draw; + +public class DrawOverloadTests : ComponentBaseTests +{ + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawWithOverloadHorizontal() + { + var canvas = Mock.Of(w => w.Size == new Size(9, 1)); + var root = Prepare.Container(Component, Component); + root.SetOrientationHorizontal(); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionOnce(4, 0); + Mock.Get(canvas).Verify(w => w.Paint("Lore"), Times.Exactly(2)); + } + + [Theory] + [InlineData(4, 4, new[] { 0, 1, 2, 3 })] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawWithOverloadVertical(int rootWidth, int rootHeight, int[] expectedTopPositions) + { + var canvas = Mock.Of(w => w.Size == new Size(rootWidth, rootHeight)); + Component.SetContent("Lorem\nLorem\nLorem"); + var root = Prepare.Container(Component, Component); + root.SetOrientationVertical(); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + foreach (var expectedTopPosition in expectedTopPositions) + { + Mock.Get(canvas).VerifyPositionOnce(0, expectedTopPosition); + } + + Mock.Get(canvas).Verify(w => w.Paint("Lore"), Times.Exactly(rootHeight)); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/DrawResizingTests.cs b/tests/TUI.Engine.Tests/Draw/DrawResizingTests.cs new file mode 100644 index 0000000..3505af5 --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/DrawResizingTests.cs @@ -0,0 +1,52 @@ +using Moq; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; +using TUI.Engine.Tests.Stubs; + +namespace TUI.Engine.Tests.Draw; + +public class DrawResizingTests : ComponentBaseTests +{ + private readonly TestContainer _container; + private readonly TestContainer _root; + private readonly ICanvas _canvas; + + public DrawResizingTests() + { + var component = Prepare.Component(); + _canvas = Mock.Of(w => w.Size == new Size(20, 2)); + _container = Prepare.Container(component); + _root = Prepare.Container(_container, component); + _root.SetOrientationHorizontal(); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawResizingFixedContainer() + { + _container.SetFixed(Orientation.Horizontal, 6); + _container.SetFixed(Orientation.Vertical, 2); + + Craftsman(_canvas).Draw(_root, Position.Default, _canvas.Size); + + Mock.Get(_canvas).VerifyPositionOnce(Position.Default); + Mock.Get(_canvas).VerifyPositionOnce(6, 0); + Mock.Get(_canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(2)); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawResizingAdaptiveContainer() + { + _container.SetAdaptive(Orientation.Horizontal); + + Craftsman(_canvas).Draw(_root, Position.Default, _canvas.Size); + + Mock.Get(_canvas).VerifyPositionOnce(Position.Default); + Mock.Get(_canvas).VerifyPositionOnce(10, 0); + Mock.Get(_canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(2)); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/DrawStaticTests.cs b/tests/TUI.Engine.Tests/Draw/DrawStaticTests.cs new file mode 100644 index 0000000..93a6222 --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/DrawStaticTests.cs @@ -0,0 +1,38 @@ +using Moq; +using TUI.Engine.Attributes; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; +using TUI.Engine.Tests.Stubs; + +namespace TUI.Engine.Tests.Draw; + +public class DrawStaticTests : ComponentBaseTests +{ + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawStaticComponentVerticalOrientation() + { + var canvas = Mock.Of(w => w.Size == new Size(6, 4)); + + var firstComponent = Prepare.Component(); + firstComponent.SetContent("First"); + firstComponent.SetRelative(); + var secondComponent = Prepare.Component(); + secondComponent.SetContent("Second"); + secondComponent.SetAbsolute(); + var thirdComponent = Prepare.Component(); + thirdComponent.SetContent("Third"); + + var root = Prepare.Container(firstComponent, secondComponent, thirdComponent); + root.SetOrientationVertical(); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionTimes(0, 2, 2); + Mock.Get(canvas).Verify(w => w.Paint("First"), Times.Once()); + Mock.Get(canvas).Verify(w => w.Paint("Second"), Times.Once()); + Mock.Get(canvas).Verify(w => w.Paint("Third"), Times.Once()); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Draw/DrawTests.cs b/tests/TUI.Engine.Tests/Draw/DrawTests.cs new file mode 100644 index 0000000..7734af2 --- /dev/null +++ b/tests/TUI.Engine.Tests/Draw/DrawTests.cs @@ -0,0 +1,87 @@ +using Moq; +using TUI.Engine.Attributes; +using TUI.Engine.Attributes.Orientations; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering; +using TUI.Engine.Rendering.Canvas; +using TUI.Engine.Tests.Stubs; + +namespace TUI.Engine.Tests.Draw; + +public class DrawCraftsmanTests : ComponentBaseTests +{ + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawSimple() + { + var canvas = Mock.Of(w => w.Size == new Size(9, 1)); + + Craftsman(canvas).Draw(Component, Position.Default, canvas.Size); + + Mock.Get(canvas).Verify(w => w.SetPencil(Position.Default), Times.Once()); + Mock.Get(canvas).Verify(w => w.Paint("Lorem"), Times.Once()); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawVerticalWithDoubleComponent() + { + var canvas = Mock.Of(w => w.Size == new Size(10, 2)); + var root = Prepare.Container(Component, Component); + root.SetOrientationVertical(); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionOnce(0, 1); + Mock.Get(canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(2)); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawHorizontalWithDoubleComponent() + { + var canvas = Mock.Of(w => w.Size == new Size(10, 1)); + var container = Prepare.Container(Component, Component); + container.SetOrientationHorizontal(); + + Craftsman(canvas).Draw(container, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionOnce(5, 0); + Mock.Get(canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(2)); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawWithMultipleComponent() + { + var canvas = Mock.Of(w => w.Size == new Size(24, 1)); + var root = Prepare.Container(Component, Component, Component, Component); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionOnce(6, 0); + Mock.Get(canvas).VerifyPositionOnce(12, 0); + Mock.Get(canvas).VerifyPositionOnce(18, 0); + Mock.Get(canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(4)); + } + + [Fact] + [Trait("Category", nameof(IDrawable.Draw))] + public void DrawWithContainerAndComponent() + { + var canvas = Mock.Of(w => w.Size == new Size(10, 2)); + var container = Prepare.Container(Component); + var root = Prepare.Container(container, Component); + root.SetAdaptive(Orientation.Vertical); + root.SetOrientationVertical(); + + Craftsman(canvas).Draw(root, Position.Default, canvas.Size); + + Mock.Get(canvas).VerifyPositionOnce(Position.Default); + Mock.Get(canvas).VerifyPositionOnce(0, 1); + Mock.Get(canvas).Verify(w => w.Paint("Lorem"), Times.Exactly(2)); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/MockExtensions.cs b/tests/TUI.Engine.Tests/MockExtensions.cs new file mode 100644 index 0000000..f430f49 --- /dev/null +++ b/tests/TUI.Engine.Tests/MockExtensions.cs @@ -0,0 +1,22 @@ +using Moq; +using TUI.Engine.Nodes; +using TUI.Engine.Rendering.Canvas; + +namespace TUI.Engine.Tests; + +public static class MockExtensions +{ + public static void VerifyPositionOnce(this Mock mock, int left, int top) where T : class, ICanvas + { + mock.Verify(w => w.SetPencil(new Position(left, top)), Times.Exactly(1)); + } + public static void VerifyPositionTimes(this Mock mock, int left, int top, int times) where T : class, ICanvas + { + mock.Verify(w => w.SetPencil(new Position(left, top)), Times.Exactly(times)); + } + + public static void VerifyPositionOnce(this Mock mock, Position position) where T : class, ICanvas + { + mock.Verify(w => w.SetPencil(position), Times.Exactly(1)); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Primitives/IntegerTests.cs b/tests/TUI.Engine.Tests/Primitives/IntegerTests.cs new file mode 100644 index 0000000..57a6e8c --- /dev/null +++ b/tests/TUI.Engine.Tests/Primitives/IntegerTests.cs @@ -0,0 +1,28 @@ +using FluentAssertions; + +namespace TUI.Engine.Tests.Primitives; + +public class IntegerTests +{ + [Theory] + [Trait("Category", "Primitives")] + [InlineData(5, 10, 5)] + [InlineData(5, 5, 5)] + [InlineData(5, 3, 3)] + public void Max(int value, int max, int expected) + { + var result = value.Max(max); + result.Should().Be(expected); + } + + [Theory] + [Trait("Category", "Primitives")] + [InlineData(5, 10, 10)] + [InlineData(5, 5, 5)] + [InlineData(5, 3, 5)] + public void Min(int value, int min, int expected) + { + var result = value.Min(min); + result.Should().Be(expected); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Stubs/Prepare.cs b/tests/TUI.Engine.Tests/Stubs/Prepare.cs new file mode 100644 index 0000000..428dff2 --- /dev/null +++ b/tests/TUI.Engine.Tests/Stubs/Prepare.cs @@ -0,0 +1,22 @@ +using TUI.Engine.Attributes.Alignments; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Tests.Stubs; + +public static class Prepare +{ + public static TestComponent Component() + { + var testComponent = new TestComponent(); + testComponent.SetAlignment(Horizontal.Left); + testComponent.SetAlignment(Vertical.Top); + return testComponent; + } + + public static TestContainer Container(params INode[] nodes) + { + var testContainer = new TestContainer(); + testContainer.SetNodes(nodes); + return testContainer; + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Stubs/TestComponent.cs b/tests/TUI.Engine.Tests/Stubs/TestComponent.cs new file mode 100644 index 0000000..e574c1f --- /dev/null +++ b/tests/TUI.Engine.Tests/Stubs/TestComponent.cs @@ -0,0 +1,19 @@ +using TUI.Engine.Attributes; +using TUI.Engine.Components; + +namespace TUI.Engine.Tests.Stubs; + +public class TestComponent : ComponentBase +{ + private string _content = "Lorem"; + + public void SetContent(string content) + { + _content = content; + } + + protected override Sketch DrawComponent(Size minSize) + { + return new Sketch(_content); + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/Stubs/TestContainer.cs b/tests/TUI.Engine.Tests/Stubs/TestContainer.cs new file mode 100644 index 0000000..07d8d20 --- /dev/null +++ b/tests/TUI.Engine.Tests/Stubs/TestContainer.cs @@ -0,0 +1,20 @@ +using TUI.Engine.Containers; +using TUI.Engine.Nodes; + +namespace TUI.Engine.Tests.Stubs; + +public class TestContainer : ContainerBase +{ + private readonly Nodes.Nodes _nodes = new(); + + public override Nodes.Nodes GetNodes() + { + return _nodes; + } + + public TestContainer SetNodes(params INode[] nodes) + { + _nodes.AddRange(nodes); + return this; + } +} \ No newline at end of file diff --git a/tests/TUI.Engine.Tests/TUI.Engine.Tests.csproj b/tests/TUI.Engine.Tests/TUI.Engine.Tests.csproj new file mode 100644 index 0000000..ba7433b --- /dev/null +++ b/tests/TUI.Engine.Tests/TUI.Engine.Tests.csproj @@ -0,0 +1,33 @@ + + + + net7.0 + enable + enable + + false + true + TUI.Engine.Tests + TUI.Engine.Tests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/TUI.Engine.Tests/Usings.cs b/tests/TUI.Engine.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/TUI.Engine.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file