From 11a528766cff44469b7add688843d4edc4e889c5 Mon Sep 17 00:00:00 2001
From: JFronny <33260128+JFronny@users.noreply.github.com>
Date: Fri, 22 May 2020 21:12:09 +0200
Subject: [PATCH] Simple component system for TUI apps, some basic components
---
.../.idea/projectSettingsUpdater.xml | 2 +-
CC-Functions.sln | 12 ++
CLITest/CLITest.csproj | 13 ++
CLITest/Program.cs | 79 +++++++++
Commandline/Commandline.csproj | 36 ++++
Commandline/TUI/Button.cs | 34 ++++
Commandline/TUI/CheckBox.cs | 61 +++++++
Commandline/TUI/Control.cs | 81 +++++++++
Commandline/TUI/DiffDraw.cs | 102 +++++++++++
Commandline/TUI/InputEventArgs.cs | 12 ++
Commandline/TUI/Label.cs | 31 ++++
Commandline/TUI/Panel.cs | 44 +++++
Commandline/TUI/Pixel.cs | 60 +++++++
Commandline/TUI/Screen.cs | 111 ++++++++++++
Commandline/TUI/Slider.cs | 94 +++++++++++
Commandline/TUI/TextBox.cs | 158 ++++++++++++++++++
Commandline/TableParser.cs | 107 ++++++++++++
Misc/ArrayFormatter.cs | 72 +++++++-
Misc/SpecialChars.cs | 7 +
19 files changed, 1107 insertions(+), 9 deletions(-)
create mode 100644 CLITest/CLITest.csproj
create mode 100644 CLITest/Program.cs
create mode 100644 Commandline/Commandline.csproj
create mode 100644 Commandline/TUI/Button.cs
create mode 100644 Commandline/TUI/CheckBox.cs
create mode 100644 Commandline/TUI/Control.cs
create mode 100644 Commandline/TUI/DiffDraw.cs
create mode 100644 Commandline/TUI/InputEventArgs.cs
create mode 100644 Commandline/TUI/Label.cs
create mode 100644 Commandline/TUI/Panel.cs
create mode 100644 Commandline/TUI/Pixel.cs
create mode 100644 Commandline/TUI/Screen.cs
create mode 100644 Commandline/TUI/Slider.cs
create mode 100644 Commandline/TUI/TextBox.cs
create mode 100644 Commandline/TableParser.cs
create mode 100644 Misc/SpecialChars.cs
diff --git a/.idea/.idea.CC-Functions/.idea/projectSettingsUpdater.xml b/.idea/.idea.CC-Functions/.idea/projectSettingsUpdater.xml
index 7515e76..4bb9f4d 100644
--- a/.idea/.idea.CC-Functions/.idea/projectSettingsUpdater.xml
+++ b/.idea/.idea.CC-Functions/.idea/projectSettingsUpdater.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/CC-Functions.sln b/CC-Functions.sln
index 283bc70..3b833f4 100644
--- a/CC-Functions.sln
+++ b/CC-Functions.sln
@@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misc", "Misc\Misc.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "W32.Test", "W32.Test\W32.Test.csproj", "{6121A6D3-7C73-4EDE-A5A9-09DC52150824}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commandline", "Commandline\Commandline.csproj", "{08C16A0B-A69D-4229-BC6D-F6949CA7BE76}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLITest", "CLITest\CLITest.csproj", "{3FAB0713-3021-4C6A-BF4A-ABBD542634A6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +31,14 @@ Global
{6121A6D3-7C73-4EDE-A5A9-09DC52150824}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6121A6D3-7C73-4EDE-A5A9-09DC52150824}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6121A6D3-7C73-4EDE-A5A9-09DC52150824}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08C16A0B-A69D-4229-BC6D-F6949CA7BE76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08C16A0B-A69D-4229-BC6D-F6949CA7BE76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08C16A0B-A69D-4229-BC6D-F6949CA7BE76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08C16A0B-A69D-4229-BC6D-F6949CA7BE76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3FAB0713-3021-4C6A-BF4A-ABBD542634A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3FAB0713-3021-4C6A-BF4A-ABBD542634A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3FAB0713-3021-4C6A-BF4A-ABBD542634A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3FAB0713-3021-4C6A-BF4A-ABBD542634A6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/CLITest/CLITest.csproj b/CLITest/CLITest.csproj
new file mode 100644
index 0000000..86a92c3
--- /dev/null
+++ b/CLITest/CLITest.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ 8
+
+
+
+
+
+
+
diff --git a/CLITest/Program.cs b/CLITest/Program.cs
new file mode 100644
index 0000000..456a92f
--- /dev/null
+++ b/CLITest/Program.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Drawing;
+using System.Threading;
+using CC_Functions.Commandline.TUI;
+
+namespace CLITest
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.BackgroundColor = ConsoleColor.Black;
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.Clear();
+ Screen screen = new Screen(40, 20);
+ Button btn1 = new Button("Test")
+ {
+ Point = new Point(2, 0)
+ };
+ screen.Controls.Add(btn1);
+ btn1.Click += (screen1, eventArgs) =>
+ {
+ DiffDraw.FullDraw(true);
+ };
+ Label lab1 = new Label("Meem")
+ {
+ Point = new Point(2, 1)
+ };
+ screen.Controls.Add(lab1);
+ screen.Controls.Add(new Label("Saas\nSoos")
+ {
+ Point = new Point(2, 2)
+ });
+ Button btn2 = new Button("X");
+ screen.Controls.Add(btn2);
+
+ CheckBox box = new CheckBox("Are u gae?")
+ {
+ Point = new Point(2, 3)
+ };
+ screen.Controls.Add(box);
+ box.CheckedChanged += (screen1, eventArgs) =>
+ {
+ lab1.Content = box.Checked ? "Sas" : "Meem";
+ };
+
+ TextBox tbox = new TextBox("Hello\nWorld1\n\nHow are u?")
+ {
+ Size = new Size(20, 10),
+ Point = new Point(0, 6)
+ };
+ screen.Controls.Add(tbox);
+
+ Slider slider = new Slider
+ {
+ Point = new Point(2, 4),
+ Size = new Size(8, 2),
+ MaxValue = 75,
+ StepSize = 14,
+ MinValue = -3,
+ Value = 7
+ };
+ screen.Controls.Add(slider);
+
+ bool visible = true;
+ btn2.Click += (screen1, eventArgs) => visible = false;
+ screen.Close += (screen1, eventArgs) => visible = false;
+ screen.Render();
+ while (visible)
+ {
+ Thread.Sleep(50);
+ screen.ReadInput();
+ }
+ Console.ResetColor();
+ Console.Clear();
+ Console.WriteLine("Bye");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Commandline/Commandline.csproj b/Commandline/Commandline.csproj
new file mode 100644
index 0000000..1186f20
--- /dev/null
+++ b/Commandline/Commandline.csproj
@@ -0,0 +1,36 @@
+
+
+
+ netstandard2.1
+ Library
+ CC_Functions.Commandline
+ CC_Functions.Commandline
+ 8
+ false
+ CC-Functions.Commandline
+ CC-Functions.Commandline
+ CC24
+ Random pieces of code used across my projects. CLI/TUI extensions
+ Copyright 2020
+ https://github.com/JFronny/CC-Functions
+ https://github.com/JFronny/CC-Functions.git
+ git
+ 1.1.*
+ 1.0.0.0
+ 0.0
+ 1.1.$(VersionSuffix)
+
+
+
+ bin\Debug\Commandline.xml
+
+
+
+ bin\Release\Commandline.xml
+
+
+
+
+
+
+
diff --git a/Commandline/TUI/Button.cs b/Commandline/TUI/Button.cs
new file mode 100644
index 0000000..d943b14
--- /dev/null
+++ b/Commandline/TUI/Button.cs
@@ -0,0 +1,34 @@
+using System.Drawing;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Button : Control
+ {
+ public string Content;
+ public Button(string content)
+ {
+ Content = content;
+ char[,] tmp = Content.ToNDArray2D();
+ Size = new Size(tmp.GetLength(0), tmp.GetLength(1));
+ }
+
+ public override Pixel[,] Render()
+ {
+ char[,] inp = Content.ToNDArray2D();
+ inp = inp.Resize(Size.Width, Size.Height);
+ Pixel[,] output = new Pixel[Size.Width, Size.Height];
+ for (int x = 0; x < Size.Width; x++)
+ for (int y = 0; y < Size.Height; y++)
+ output[x, y] = new Pixel(Selected ? ForeColor : BackColor, Selected ? BackColor : ForeColor, inp[x, y]);
+ return output;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+ //ignored for [Render]s sake, do not use
+ }
+
+ public override bool Selectable { get; } = true;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/CheckBox.cs b/Commandline/TUI/CheckBox.cs
new file mode 100644
index 0000000..3472213
--- /dev/null
+++ b/Commandline/TUI/CheckBox.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Drawing;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class CheckBox : Control
+ {
+ public string Content;
+ public bool Checked = false;
+ public CheckBox(string content)
+ {
+ Content = content;
+ Input += (screen, args) =>
+ {
+ switch (args.Info.Key)
+ {
+ case ConsoleKey.LeftArrow:
+ case ConsoleKey.RightArrow:
+ case ConsoleKey.Spacebar:
+ Checked = !Checked;
+ CheckedChanged?.Invoke(screen, args);
+ break;
+ }
+ };
+ Click += (screen, args) =>
+ {
+ Checked = !Checked;
+ CheckedChanged?.Invoke(screen, args);
+ };
+ }
+
+ public override Pixel[,] Render()
+ {
+ char[,] inp1 = Content.ToNDArray2D();
+ char[,] inp = new char[inp1.GetLength(0), inp1.GetLength(1) + 4];
+ inp.Populate(' ');
+ inp1.CopyTo(inp, new Point(4, 0));
+ inp[0, 0] = '[';
+ inp[0, 1] = Checked ? 'X' : SpecialChars.empty;
+ inp[0, 2] = ']';
+ int w = inp.GetLength(0);
+ int h = inp.GetLength(1);
+ Pixel[,] output = new Pixel[w, h];
+ for (int x = 0; x < w; x++)
+ for (int y = 0; y < h; y++)
+ output[x, y] = new Pixel(Selected ? ForeColor : BackColor, Selected ? BackColor : ForeColor, inp[x, y]);
+ Size = new Size(w, h);
+ return output;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+ //ignored for [Render]s sake, do not use
+ }
+
+ public override bool Selectable { get; } = true;
+ public delegate void OnCheckedChanged(Screen screen, EventArgs e);
+ public event OnClick CheckedChanged;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/Control.cs b/Commandline/TUI/Control.cs
new file mode 100644
index 0000000..7ff955c
--- /dev/null
+++ b/Commandline/TUI/Control.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Drawing;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public abstract class Control
+ {
+ private Point _point;
+ private Size _size;
+ public abstract Pixel[,] Render();
+ protected abstract void Resize(int width, int height);
+ private void Resize(Size size) => Resize(size.Width, size.Height);
+
+ public Size Size
+ {
+ set
+ {
+ _size = value;
+ Resize(value);
+ }
+ get => _size;
+ }
+
+ public Point Point
+ {
+ get => _point;
+ set => _point = value;
+ }
+
+ public ConsoleColor ForeColor { get; set; } = Console.ForegroundColor;
+ public ConsoleColor BackColor { get; set; } = Console.BackgroundColor;
+ public abstract bool Selectable { get; }
+
+ ///
+ /// Called when [enter] is pressed while the control is selected
+ ///
+ /// An instance of the calling screen
+ /// Args
+ public delegate void OnClick(Screen screen, EventArgs e);
+ ///
+ /// Called when [enter] is pressed while the control is selected
+ ///
+ public event OnClick Click;
+ ///
+ /// Called when the control is selected and unknown input is given
+ ///
+ /// An instance of the calling screen
+ /// Args
+ public delegate void OnInput(Screen screen, InputEventArgs e);
+ ///
+ /// Called when the control is selected and unknown input is given
+ ///
+ public event OnInput Input;
+ ///
+ /// Whether the object is selected. Used internally and for drawing
+ ///
+ public bool Selected { get; internal set; } = false;
+ ///
+ /// Invokes click events
+ ///
+ /// The calling screen
+ internal void InvokeClick(Screen screen)
+ {
+ Click?.Invoke(screen, new EventArgs());
+ }
+ ///
+ /// Invokes input events
+ ///
+ /// The calling screen
+ /// The input data
+ internal void InvokeInput(Screen screen, ConsoleKeyInfo info)
+ {
+ Input?.Invoke(screen, new InputEventArgs(info));
+ }
+
+ ///
+ /// Whether the control should be rendered
+ ///
+ public bool Visible = true;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/DiffDraw.cs b/Commandline/TUI/DiffDraw.cs
new file mode 100644
index 0000000..65033f8
--- /dev/null
+++ b/Commandline/TUI/DiffDraw.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Drawing;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ ///
+ /// Provides differential drawing of a char[,] Do not use in combination with System.Console
+ ///
+ public static class DiffDraw
+ {
+ private static Pixel[,] Screen { get; set; } = new Pixel[0, 0];
+ private static Pixel[,] _last = new Pixel[0,0];
+ public static int Width => Screen.GetLength(1);
+ public static int Height => Screen.GetLength(0);
+
+ public static void Draw(bool color)
+ {
+ Console.CursorTop = 0;
+ Console.CursorLeft = 0;
+ ConsoleColor fcol = Console.ForegroundColor;
+ ConsoleColor bcol = Console.BackgroundColor;
+ int width = Width;
+ int height = Height;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ Pixel tmp1 = Screen[y, x];
+ if (tmp1 == _last[y, x]) continue;
+ if (color)
+ {
+ Console.ForegroundColor = tmp1.ForeColor;
+ Console.BackgroundColor = tmp1.BackColor;
+ }
+ Console.CursorLeft = x;
+ Console.Write(tmp1);
+ }
+ Console.WriteLine();
+ Console.CursorLeft = 0;
+ }
+ Console.ForegroundColor = fcol;
+ Console.BackgroundColor = bcol;
+ _last = Screen;
+ }
+
+ public static void FullDraw(bool color)
+ {
+ Console.CursorTop = 0;
+ Console.CursorLeft = 0;
+ ConsoleColor fcol = Console.ForegroundColor;
+ ConsoleColor bcol = Console.BackgroundColor;
+ Console.Clear();
+ int width = Width;
+ int height = Height;
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ Pixel tmp1 = Screen[y, x];
+ if (color)
+ {
+ Console.ForegroundColor = tmp1.ForeColor;
+ Console.BackgroundColor = tmp1.BackColor;
+ }
+ Console.CursorLeft = x;
+ Console.Write(tmp1);
+ }
+ Console.WriteLine();
+ Console.CursorLeft = 0;
+ }
+ Console.ForegroundColor = fcol;
+ Console.BackgroundColor = bcol;
+ _last = Screen;
+ }
+
+ public static char Get(Point p) => Get(p.X, p.Y);
+ public static char Get(int x, int y) => Screen[y, x].Content;
+ public static ConsoleColor GetForeColor(Point p) => GetForeColor(p.X, p.Y);
+ public static ConsoleColor GetForeColor(int x, int y) => Screen[y, x].ForeColor;
+ public static ConsoleColor GetBackColor(Point p) => GetBackColor(p.X, p.Y);
+ public static ConsoleColor GetBackColor(int x, int y) => Screen[y, x].BackColor;
+
+ public static void Set(Point p, Pixel c) => Set(p.X, p.Y, c);
+
+ public static void Set(int x, int y, Pixel c) => Screen[y, x] = c;
+
+ public static void Clear() => Clear(Width, Height);
+
+ public static void Clear(int width, int height)
+ {
+ Screen = new Pixel[height, width];
+ _last = _last.Resize(height, width);
+ }
+
+ public static void Clear(Pixel[,] content)
+ {
+ Screen = content;
+ _last = _last.Resize(Height, Width);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/InputEventArgs.cs b/Commandline/TUI/InputEventArgs.cs
new file mode 100644
index 0000000..f528784
--- /dev/null
+++ b/Commandline/TUI/InputEventArgs.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class InputEventArgs : EventArgs
+ {
+ private readonly ConsoleKeyInfo _info;
+ public ConsoleKeyInfo Info => _info;
+
+ public InputEventArgs(ConsoleKeyInfo info) => _info = info;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/Label.cs b/Commandline/TUI/Label.cs
new file mode 100644
index 0000000..27f3a67
--- /dev/null
+++ b/Commandline/TUI/Label.cs
@@ -0,0 +1,31 @@
+using System.Drawing;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Label : Control
+ {
+ public string Content;
+ public Label(string content) => Content = content;
+
+ public override Pixel[,] Render()
+ {
+ char[,] inp = Content.ToNDArray2D();
+ int w = inp.GetLength(0);
+ int h = inp.GetLength(1);
+ Pixel[,] output = new Pixel[w, h];
+ for (int x = 0; x < w; x++)
+ for (int y = 0; y < h; y++)
+ output[x, y] = new Pixel(BackColor, ForeColor, inp[x, y]);
+ Size = new Size(w, h);
+ return output;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+ //ignored for [Render]s sake, do not use
+ }
+
+ public override bool Selectable { get; } = false;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/Panel.cs b/Commandline/TUI/Panel.cs
new file mode 100644
index 0000000..a47f244
--- /dev/null
+++ b/Commandline/TUI/Panel.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Linq;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Panel : Control
+ {
+ public List Controls = new List();
+
+ public override Pixel[,] Render()
+ {
+ Pixel[,] tmp = new Pixel[Size.Height, Size.Width];
+ tmp.Populate(new Pixel(BackColor, ForeColor, SpecialChars.empty));
+ foreach (Control control in Controls)
+ {
+ if (control.Visible)
+ {
+ Pixel[,] render = control.Render();
+ render.CopyTo(tmp, control.Point);
+ }
+ }
+ return tmp;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+ }
+
+ public override bool Selectable { get; } = false;
+
+ public Control[] EnumerateRecursive()
+ {
+ List output = Controls.ToList();
+ int i = 0;
+ while (i < output.Count)
+ {
+ if (output[i] is Panel p) output.AddRange(p.EnumerateRecursive().Where(s => !output.Contains(s)));
+ i++;
+ }
+ return output.ToArray();
+ }
+ }
+}
diff --git a/Commandline/TUI/Pixel.cs b/Commandline/TUI/Pixel.cs
new file mode 100644
index 0000000..c76a9ba
--- /dev/null
+++ b/Commandline/TUI/Pixel.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Pixel
+ {
+ private sealed class ColorContentEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(Pixel x, Pixel y)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (ReferenceEquals(x, null)) return false;
+ if (ReferenceEquals(y, null)) return false;
+ if (x.GetType() != y.GetType()) return false;
+ return x.GetHashCode().Equals(y.GetHashCode());
+ }
+
+ public int GetHashCode(Pixel obj) => obj.GetHashCode();
+ }
+
+ public static IEqualityComparer ColorContentComparer { get; } = new ColorContentEqualityComparer();
+
+ protected bool Equals(Pixel other) => ColorContentComparer.Equals(this, other);
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((Pixel) obj);
+ }
+
+ public override int GetHashCode() => HashCode.Combine((int) BackColor, (int) ForeColor, Content);
+
+ public ConsoleColor BackColor;
+ public ConsoleColor ForeColor;
+ public char Content;
+
+ public Pixel(ConsoleColor backColor, ConsoleColor foreColor, char content)
+ {
+ BackColor = backColor;
+ ForeColor = foreColor;
+ Content = content;
+ }
+
+ public Pixel(char content) : this(Console.BackgroundColor, Console.ForegroundColor, content)
+ {
+ }
+
+ public Pixel() : this(' ')
+ {
+ }
+
+ public static bool operator ==(Pixel a, Pixel b) => a.Equals(b);
+ public static bool operator !=(Pixel a, Pixel b) => !a.Equals(b);
+
+ public override string ToString() => Content.ToString();
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/Screen.cs b/Commandline/TUI/Screen.cs
new file mode 100644
index 0000000..ea65a3c
--- /dev/null
+++ b/Commandline/TUI/Screen.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Drawing;
+using System.Linq;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Screen : Panel
+ {
+ public readonly bool Color;
+ public int TabPoint = 0;
+ ///
+ /// Creates a screen object. Multiple can be instantiated but drawing one overrides others. Use panels for that
+ ///
+ /// The screens with
+ /// The screens height
+ /// Whether to output in color
+ public Screen(int width, int height, bool color = true)
+ {
+ Color = color;
+ Resize(width, height);
+ Tab();
+ }
+
+ ///
+ /// Resizes the screen. Make sure that this does not exceed the console size
+ ///
+ ///
+ ///
+ public void Resize(int width, int height)
+ {
+ Size = new Size(width, height);
+ DiffDraw.Clear(width, height);
+ }
+
+ ///
+ /// Renders the screen, draws it to the console and outputs the new state
+ ///
+ /// The new state of the screen
+ public Pixel[,] Render()
+ {
+ Pixel[,] tmp = base.Render();
+ DiffDraw.Clear(tmp);
+ DiffDraw.Draw(Color);
+ return tmp;
+ }
+
+ public void ReadInput()
+ {
+ while (Console.KeyAvailable)
+ {
+ Control[] controls = EnumerateRecursive();
+ Control[] selectable = controls.Where(s => s.Selectable).ToArray();
+ ConsoleKeyInfo input = Console.ReadKey();
+ switch (input.Key)
+ {
+ case ConsoleKey.Tab:
+ Tab(selectable, (input.Modifiers & ConsoleModifiers.Shift) != 0);
+ break;
+ case ConsoleKey.Enter:
+ if (selectable.Any() && selectable.Length >= TabPoint)
+ {
+ selectable[TabPoint].InvokeClick(this);
+ Render();
+ }
+ break;
+ case ConsoleKey.Escape:
+ Close?.Invoke(this, new EventArgs());
+ break;
+ default:
+ if (selectable.Any() && selectable.Length >= TabPoint)
+ {
+ selectable[TabPoint].InvokeInput(this, input);
+ Render();
+ }
+ break;
+ }
+ }
+ }
+
+ public void Tab(bool positive = true)
+ {
+ Control[] controls = EnumerateRecursive();
+ Control[] selectable = controls.Where(s => s.Selectable).ToArray();
+ Tab(selectable, positive);
+ }
+
+ public void Tab(Control[] selectable, bool positive)
+ {
+ if (selectable.Any())
+ {
+ if (positive)
+ {
+ TabPoint++;
+ if (TabPoint >= selectable.Length) TabPoint = 0;
+ }
+ else
+ {
+ TabPoint--;
+ if (TabPoint < 0) TabPoint = selectable.Length - 1;
+ }
+ foreach (Control control in selectable) control.Selected = false;
+ selectable[TabPoint].Selected = true;
+ Render();
+ }
+ }
+
+ public delegate void OnClose(Screen screen, EventArgs e);
+
+ public event OnClick Close;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/Slider.cs b/Commandline/TUI/Slider.cs
new file mode 100644
index 0000000..f324246
--- /dev/null
+++ b/Commandline/TUI/Slider.cs
@@ -0,0 +1,94 @@
+using System;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class Slider : Control
+ {
+ public Slider()
+ {
+ Input += (screen, args) =>
+ {
+ switch (args.Info.Key)
+ {
+ case ConsoleKey.LeftArrow:
+ _value -= StepSize;
+ if (_value < MinValue)
+ _value = MinValue;
+ Value = _value;
+ break;
+ case ConsoleKey.RightArrow:
+ _value += StepSize;
+ if (_value > MaxValue)
+ _value = MaxValue;
+ Value = _value;
+ break;
+ }
+ };
+ }
+
+ public int MaxValue
+ {
+ get => _maxValue;
+ set
+ {
+ if (value > MinValue && value >= Value)
+ _maxValue = value;
+ else
+ throw new ArgumentOutOfRangeException("MaxValue must be larger than MinValue and equal to or larger than Value");
+ }
+ }
+
+ public int MinValue
+ {
+ get => _minValue;
+ set
+ {
+ if (value < MaxValue && value <= Value)
+ _minValue = value;
+ else
+ throw new ArgumentOutOfRangeException("MaxValue must be larger than MinValue and equal to or smaller than Value");
+ }
+ }
+
+ public int Value
+ {
+ get => _value;
+ set
+ {
+ if (value <= MaxValue && value >= MinValue)
+ _value = value;
+ else
+ throw new ArgumentOutOfRangeException("Value must be between MinValue and MaxValue");
+ }
+ }
+
+ public int StepSize = 1;
+ private int _value = 5;
+ private int _maxValue = 10;
+ private int _minValue = 0;
+
+ public override Pixel[,] Render()
+ {
+ int delta = MaxValue - MinValue;
+ int litValLen = Math.Max(MaxValue.ToString().Length, MinValue.ToString().Length);
+ int prevpts = Math.Max((Value - MinValue) * Size.Width / delta - litValLen - 2, 0);
+ int postpts = Math.Max(Size.Width - prevpts - litValLen - 2, 0);
+ char[,] rend = $"{new string('=', prevpts)}[{Value.ToString($"D{litValLen}")}]{new string('=', postpts)}".ToNDArray2D();
+ int f1 = rend.GetLength(0);
+ int f2 = rend.GetLength(1);
+ Pixel[,] output = new Pixel[f1, f2];
+ output.Populate(new Pixel());
+ for (int i = 0; i < f1; i++)
+ for (int j = 0; j < f2; j++) output[i, j] = new Pixel(Selected ? ForeColor : BackColor, Selected ? BackColor : ForeColor, rend[i, j]);
+ return output;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+
+ }
+
+ public override bool Selectable { get; } = true;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TUI/TextBox.cs b/Commandline/TUI/TextBox.cs
new file mode 100644
index 0000000..dc2b77c
--- /dev/null
+++ b/Commandline/TUI/TextBox.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using CC_Functions.Misc;
+
+namespace CC_Functions.Commandline.TUI
+{
+ public class TextBox : Control
+ {
+ public string Content;
+ private string[] Lines
+ {
+ get => Content.Split('\n');
+ set => Content = string.Join('\n', value);
+ }
+
+ public Point Cursor = new Point(0, 0);
+ public TextBox(string content)
+ {
+ Content = content;
+ Input += (screen, args) =>
+ {
+ ProcessInput(args.Info.Key, args.Info);
+ };
+ Click += (screen, args) => ProcessInput(ConsoleKey.Enter, new ConsoleKeyInfo());
+ }
+
+ private void ProcessInput(ConsoleKey key, ConsoleKeyInfo info)
+ {
+ string[] lines = Lines;
+ List tmp;
+ int tmplen;
+ switch (key)
+ {
+ case ConsoleKey.LeftArrow:
+ Cursor.X--;
+ if (Cursor.X < 0)
+ {
+ if (Cursor.Y > 0) Cursor.Y--;
+ ProcessInput(ConsoleKey.End, info);
+ }
+ break;
+ case ConsoleKey.RightArrow:
+ Cursor.X++;
+ if (Cursor.X >= Lines[Cursor.Y].Length)
+ {
+ Cursor.Y++;
+ Cursor.X = 0;
+ if (Cursor.Y >= Lines.Length)
+ {
+ Cursor.Y--;
+ ProcessInput(ConsoleKey.End, info);
+ }
+ }
+ break;
+ case ConsoleKey.UpArrow:
+ if (Cursor.Y > 0)
+ {
+ Cursor.Y--;
+ if (Cursor.X >= Lines[Cursor.Y].Length) ProcessInput(ConsoleKey.End, info);
+ }
+ else
+ Cursor.X = 0;
+ break;
+ case ConsoleKey.DownArrow:
+ if (Cursor.Y < Lines.Length - 1)
+ {
+ Cursor.Y++;
+ if (Cursor.X >= Lines[Cursor.Y].Length) ProcessInput(ConsoleKey.End, info);
+ }
+ else
+ ProcessInput(ConsoleKey.End, info);
+ break;
+ case ConsoleKey.Home:
+ Cursor.X = 0;
+ break;
+ case ConsoleKey.End:
+ Cursor.X = Math.Max(Lines[Cursor.Y].Length - 1, 0);
+ break;
+ case ConsoleKey.PageUp:
+ for (int i = 0; i < 5; i++)
+ ProcessInput(ConsoleKey.UpArrow, info);
+ break;
+ case ConsoleKey.PageDown:
+ for (int i = 0; i < 5; i++)
+ ProcessInput(ConsoleKey.DownArrow, info);
+ break;
+ case ConsoleKey.Delete:
+ if (lines[Cursor.Y].Length > Cursor.X)
+ lines[Cursor.Y] = lines[Cursor.Y].Remove(Cursor.X, 1);
+ Lines = lines;
+ break;
+ case ConsoleKey.Backspace:
+ if (Cursor.X > 0 && lines[Cursor.Y].Length > 0)
+ {
+ lines[Cursor.Y] = lines[Cursor.Y].Remove(Cursor.X - 1, 1);
+ ProcessInput(ConsoleKey.LeftArrow, info);
+ }
+ else
+ {
+ if (Cursor.Y > 0)
+ {
+ tmp = lines.ToList();
+ tmplen = tmp[Cursor.Y - 1].Length;
+ tmp[Cursor.Y - 1] += tmp[Cursor.Y];
+ tmp.RemoveAt(Cursor.Y);
+ lines = tmp.ToArray();
+ Cursor.Y--;
+ Cursor.X = tmplen - 1;
+ }
+ }
+ Lines = lines;
+ break;
+ case ConsoleKey.Enter:
+ tmp = lines.ToList();
+ lines[Cursor.Y] = lines[Cursor.Y].Insert(Cursor.X, "\n");
+ Cursor.Y++;
+ Cursor.X = 0;
+ Lines = lines;
+ break;
+ default:
+ lines[Cursor.Y] = lines[Cursor.Y].Insert(Cursor.X, info.KeyChar.ToString());
+ Lines = lines;
+ break;
+ }
+ }
+
+ public override Pixel[,] Render()
+ {
+ char[,] inp1 = Content.ToNDArray2D();
+ inp1 = inp1.Resize(Size.Height, Size.Width - 2);
+ char[,] inp = new char[Size.Width, Size.Height];
+ inp.Populate(SpecialChars.empty);
+ for (int i = 0; i < Size.Height; i++)
+ {
+ inp[0, i] = '[';
+ inp[Size.Width - 1, i] = ']';
+ }
+ for (int i = 0; i < Size.Width; i++) inp[i, Size.Height - 1] = '.';
+ inp1.Rotate().CopyTo(inp, new Point(0, 1));
+ if (Selected)
+ inp[Cursor.X + 1, Cursor.Y] = '▒';
+ Pixel[,] output = new Pixel[Size.Height, Size.Width];
+ for (int x = 0; x < Size.Width; x++)
+ for (int y = 0; y < Size.Height; y++)
+ output[y, x] = new Pixel(Selected ? ForeColor : BackColor, Selected ? BackColor : ForeColor, inp[x, y]);
+ return output;
+ }
+
+ protected override void Resize(int width, int height)
+ {
+ //ignored for [Render]s sake, do not use
+ }
+
+ public override bool Selectable { get; } = true;
+ }
+}
\ No newline at end of file
diff --git a/Commandline/TableParser.cs b/Commandline/TableParser.cs
new file mode 100644
index 0000000..7fd2e93
--- /dev/null
+++ b/Commandline/TableParser.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace CC_Functions.Commandline
+{
+ ///
+ /// Provides functions for parsing enumerables to powershell-like tables
+ ///
+ public static class TableParser
+ {
+ public static string ToStringTable(this IEnumerable values, string[] columnHeaders,
+ params Func[] valueSelectors) => ToStringTable(values.ToArray(), columnHeaders, valueSelectors);
+
+ public static string ToStringTable(this T[] values, string[] columnHeaders,
+ params Func[] valueSelectors)
+ {
+ Debug.Assert(columnHeaders.Length == valueSelectors.Length);
+
+ string[,] arrValues = new string[values.Length + 1, valueSelectors.Length];
+
+ // Fill headers
+ for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
+ arrValues[0, colIndex] = columnHeaders[colIndex];
+
+ // Fill table rows
+ for (int rowIndex = 1; rowIndex < arrValues.GetLength(0); rowIndex++)
+ for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
+ {
+ object value = valueSelectors[colIndex].Invoke(values[rowIndex - 1]);
+
+ arrValues[rowIndex, colIndex] = value != null ? value.ToString() : "null";
+ }
+
+ return ToStringTable(arrValues);
+ }
+
+ public static string ToStringTable(this string[,] arrValues)
+ {
+ int[] maxColumnsWidth = GetMaxColumnsWidth(arrValues);
+ string headerSpliter = new string('-', maxColumnsWidth.Sum(i => i + 3) - 1);
+
+ StringBuilder sb = new StringBuilder();
+ for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++)
+ {
+ for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
+ {
+ // Print cell
+ string cell = arrValues[rowIndex, colIndex];
+ cell = cell.PadRight(maxColumnsWidth[colIndex]);
+ sb.Append(" | ");
+ sb.Append(cell);
+ }
+
+ // Print end of line
+ sb.Append(" | ");
+ sb.AppendLine();
+
+ // Print splitter
+ if (rowIndex == 0)
+ {
+ sb.AppendFormat(" |{0}| ", headerSpliter);
+ sb.AppendLine();
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private static int[] GetMaxColumnsWidth(string[,] arrValues)
+ {
+ int[] maxColumnsWidth = new int[arrValues.GetLength(1)];
+ for (int colIndex = 0; colIndex < arrValues.GetLength(1); colIndex++)
+ for (int rowIndex = 0; rowIndex < arrValues.GetLength(0); rowIndex++)
+ {
+ int newLength = arrValues[rowIndex, colIndex].Length;
+ int oldLength = maxColumnsWidth[colIndex];
+
+ if (newLength > oldLength) maxColumnsWidth[colIndex] = newLength;
+ }
+
+ return maxColumnsWidth;
+ }
+
+ public static string ToStringTable(this IEnumerable values,
+ params Expression>[] valueSelectors)
+ {
+ string[] headers = valueSelectors.Select(func => GetProperty(func).Name).ToArray();
+ Func[] selectors = valueSelectors.Select(exp => exp.Compile()).ToArray();
+ return ToStringTable(values, headers, selectors);
+ }
+
+ private static PropertyInfo GetProperty(Expression> expresstion)
+ {
+ if (expresstion.Body is UnaryExpression expression)
+ if (expression.Operand is MemberExpression memberExpression)
+ return memberExpression.Member as PropertyInfo;
+
+ if (expresstion.Body is MemberExpression body) return body.Member as PropertyInfo;
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misc/ArrayFormatter.cs b/Misc/ArrayFormatter.cs
index 59ea186..28c3aef 100644
--- a/Misc/ArrayFormatter.cs
+++ b/Misc/ArrayFormatter.cs
@@ -1,15 +1,71 @@
-namespace CC_Functions.Misc
+using System;
+using System.Drawing;
+using System.Linq;
+
+namespace CC_Functions.Misc
{
public static class ArrayFormatter
{
- public static string ElementsToString(this object[] input, string separator = "\r\n")
+ public static T[,] Resize(this T[,] original, int rows, int cols)
{
- string a = "";
- for (int i = 0; i < input.Length; i++)
- a += input[i] + separator;
- if (separator.Length > 0)
- a = a.Remove(a.Length - separator.Length);
- return a;
+ T[,] newArray = new T[rows, cols];
+ int minRows = Math.Min(rows, original.GetLength(0));
+ int minCols = Math.Min(cols, original.GetLength(1));
+ for (int i = 0; i < minRows; i++)
+ for (int j = 0; j < minCols; j++)
+ newArray[i, j] = original[i, j];
+ return newArray;
+ }
+
+ public static char[,] ToNDArray2D(this string source, char defaultEl = SpecialChars.empty)
+ {
+ string[] sourceArr = source.Split('\n');
+ int width = sourceArr.Select(s => s.Length).OrderBy(s => s).Last();
+ int height = sourceArr.Length;
+ char[,] output = new char[height, width];
+ output.Populate(defaultEl);
+ for (int i = 0; i < sourceArr.Length; i++)
+ {
+ string s = sourceArr[i];
+ for (int j = 0; j < s.Length; j++)
+ output[i, j] = s[j];
+ }
+ return output;
+ }
+
+ public static void Populate(this T[] arr, T value) {
+ for ( int i = 0; i < arr.Length;i++ ) arr[i] = value;
+ }
+
+ public static void Populate(this T[,] arr, T value)
+ {
+ int w = arr.GetLength(0);
+ int h = arr.GetLength(1);
+ for (int i = 0; i < w; i++)
+ for (int j = 0; j < h; j++) arr[i, j] = value;
+ }
+
+ public static void CopyTo(this T[,] arr, T[,] target, Point offset)
+ {
+ int w = arr.GetLength(1);
+ int h = arr.GetLength(0);
+ int mw = target.GetLength(1);
+ int mh = target.GetLength(0);
+ int ow = offset.X;
+ int oh = offset.Y;
+ for (int x = ow; x < Math.Min(mw, w + ow); x++)
+ for (int y = oh; y < Math.Min(mh, h + oh); y++)
+ target[y, x] = arr[y - oh, x - ow];
+ }
+
+ public static T[,] Rotate(this T[,] arr)
+ {
+ int w = arr.GetLength(0);
+ int h = arr.GetLength(1);
+ T[,] target = new T[h, w];
+ for (int x = 0; x < w; x++)
+ for (int y = 0; y < h; y++) target[y, x] = arr[x, y];
+ return target;
}
}
}
\ No newline at end of file
diff --git a/Misc/SpecialChars.cs b/Misc/SpecialChars.cs
new file mode 100644
index 0000000..03e7372
--- /dev/null
+++ b/Misc/SpecialChars.cs
@@ -0,0 +1,7 @@
+namespace CC_Functions.Misc
+{
+ public static class SpecialChars
+ {
+ public const char empty = ' ';
+ }
+}
\ No newline at end of file