Simple component system for TUI apps, some basic components

This commit is contained in:
JFronny 2020-05-22 21:12:09 +02:00
parent b73bcce5d5
commit 11a528766c
19 changed files with 1107 additions and 9 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="1" />
<option name="vcsConfiguration" value="2" />
</component>
</project>

View File

@ -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

13
CLITest/CLITest.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Commandline\Commandline.csproj" />
</ItemGroup>
</Project>

79
CLITest/Program.cs Normal file
View File

@ -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");
}
}
}

View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType>
<RootNamespace>CC_Functions.Commandline</RootNamespace>
<AssemblyName>CC_Functions.Commandline</AssemblyName>
<LangVersion>8</LangVersion>
<Deterministic>false</Deterministic>
<PackageId>CC-Functions.Commandline</PackageId>
<Title>CC-Functions.Commandline</Title>
<Authors>CC24</Authors>
<Description>Random pieces of code used across my projects. CLI/TUI extensions</Description>
<Copyright>Copyright 2020</Copyright>
<PackageProjectUrl>https://github.com/JFronny/CC-Functions</PackageProjectUrl>
<RepositoryUrl>https://github.com/JFronny/CC-Functions.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>1.1.*</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<VersionSuffix>0.0</VersionSuffix>
<PackageVersion>1.1.$(VersionSuffix)</PackageVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>bin\Debug\Commandline.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\Commandline.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Misc\Misc.csproj" />
</ItemGroup>
</Project>

34
Commandline/TUI/Button.cs Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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; }
/// <summary>
/// Called when [enter] is pressed while the control is selected
/// </summary>
/// <param name="screen">An instance of the calling screen</param>
/// <param name="e">Args</param>
public delegate void OnClick(Screen screen, EventArgs e);
/// <summary>
/// Called when [enter] is pressed while the control is selected
/// </summary>
public event OnClick Click;
/// <summary>
/// Called when the control is selected and unknown input is given
/// </summary>
/// <param name="screen">An instance of the calling screen</param>
/// <param name="e">Args</param>
public delegate void OnInput(Screen screen, InputEventArgs e);
/// <summary>
/// Called when the control is selected and unknown input is given
/// </summary>
public event OnInput Input;
/// <summary>
/// Whether the object is selected. Used internally and for drawing
/// </summary>
public bool Selected { get; internal set; } = false;
/// <summary>
/// Invokes click events
/// </summary>
/// <param name="screen">The calling screen</param>
internal void InvokeClick(Screen screen)
{
Click?.Invoke(screen, new EventArgs());
}
/// <summary>
/// Invokes input events
/// </summary>
/// <param name="screen">The calling screen</param>
/// <param name="info">The input data</param>
internal void InvokeInput(Screen screen, ConsoleKeyInfo info)
{
Input?.Invoke(screen, new InputEventArgs(info));
}
/// <summary>
/// Whether the control should be rendered
/// </summary>
public bool Visible = true;
}
}

102
Commandline/TUI/DiffDraw.cs Normal file
View File

@ -0,0 +1,102 @@
using System;
using System.Drawing;
using CC_Functions.Misc;
namespace CC_Functions.Commandline.TUI
{
/// <summary>
/// Provides differential drawing of a char[,] Do not use in combination with System.Console
/// </summary>
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);
}
}
}

View File

@ -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;
}
}

31
Commandline/TUI/Label.cs Normal file
View File

@ -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;
}
}

44
Commandline/TUI/Panel.cs Normal file
View File

@ -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<Control> Controls = new List<Control>();
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<Control> 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();
}
}
}

60
Commandline/TUI/Pixel.cs Normal file
View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
namespace CC_Functions.Commandline.TUI
{
public class Pixel
{
private sealed class ColorContentEqualityComparer : IEqualityComparer<Pixel>
{
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<Pixel> 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();
}
}

111
Commandline/TUI/Screen.cs Normal file
View File

@ -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;
/// <summary>
/// Creates a screen object. Multiple can be instantiated but drawing one overrides others. Use panels for that
/// </summary>
/// <param name="width">The screens with</param>
/// <param name="height">The screens height</param>
/// <param name="color">Whether to output in color</param>
public Screen(int width, int height, bool color = true)
{
Color = color;
Resize(width, height);
Tab();
}
/// <summary>
/// Resizes the screen. Make sure that this does not exceed the console size
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void Resize(int width, int height)
{
Size = new Size(width, height);
DiffDraw.Clear(width, height);
}
/// <summary>
/// Renders the screen, draws it to the console and outputs the new state
/// </summary>
/// <returns>The new state of the screen</returns>
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;
}
}

94
Commandline/TUI/Slider.cs Normal file
View File

@ -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;
}
}

158
Commandline/TUI/TextBox.cs Normal file
View File

@ -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<string> 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;
}
}

107
Commandline/TableParser.cs Normal file
View File

@ -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
{
/// <summary>
/// Provides functions for parsing enumerables to powershell-like tables
/// </summary>
public static class TableParser
{
public static string ToStringTable<T>(this IEnumerable<T> values, string[] columnHeaders,
params Func<T, object>[] valueSelectors) => ToStringTable(values.ToArray(), columnHeaders, valueSelectors);
public static string ToStringTable<T>(this T[] values, string[] columnHeaders,
params Func<T, object>[] 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<T>(this IEnumerable<T> values,
params Expression<Func<T, object>>[] valueSelectors)
{
string[] headers = valueSelectors.Select(func => GetProperty(func).Name).ToArray();
Func<T, object>[] selectors = valueSelectors.Select(exp => exp.Compile()).ToArray();
return ToStringTable(values, headers, selectors);
}
private static PropertyInfo GetProperty<T>(Expression<Func<T, object>> 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;
}
}
}

View File

@ -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<T>(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<T>(this T[] arr, T value) {
for ( int i = 0; i < arr.Length;i++ ) arr[i] = value;
}
public static void Populate<T>(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<T>(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<T>(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;
}
}
}

7
Misc/SpecialChars.cs Normal file
View File

@ -0,0 +1,7 @@
namespace CC_Functions.Misc
{
public static class SpecialChars
{
public const char empty = ' ';
}
}