[Console] New approach to improved console in C# (part 1)

I’m back after a looooong time. I’ve been working on several tools, mostly on my console libraries. And I think this is the time when I can start posting about how to use it. Now, when base library is in the state that looks and works as expected and is almost polished.

So, at first I would like to thank Michał Białecki for hist Blog Post, that finally encouraged me to write about my console implementation. Mainly because it’s a nice improvement to the stuff he is / was working on, secondly because I would like people to start using what I’ve created 😉

Then. To the point.

Github for Console libraries is located HERE, please note, that there are many more components (projects) in that repository that I’m NOT going to touch in this post and are in much less usage-ready form than – so called – Root package.
You can also find some Nuggets on Nuget.org, but they are a lot outdated – they come from previous, differently divided solution. I shall soon update them and here in the post.

Main points that this root project supports:

  • Support for 24bit colors (Windows 10).
  • Color schemes support (soon importing from files)
  • Automatic adjustment to closest color (this was the first feature I’ve implemented, way in 2016… A long before discovering colorfulconsole
  • Fullscreen support
  • Easy configuration of resolution
  • Multithreading support (will write about this later)
  • Buffered and non-buffered modes (comming soon)
  • Support for improved colorfullconsole syntax (soon, if someone does that 😉


As you might figure obtaining all of this is not an easy task. And is not brainless when implementing. However I’ve tried to make it as simple and straightforward as possible.
The project contains several demo projects. In this, introductory, post im going to concentrate on the one related to the core functionalities – ObscureWare.Console.Root.Demo.

I’m referencing there 3 libraries from the project:

ObscureWare.Console.Root.Shared
Definitions of interfaces, enumerations and some helpers. Soon in Nuget.
ObscureWare.Console.Root>Dekstop
Windows implementation for console. Soon in Nuget.
ObscureWare.Console.Demo.Shared
Some demos-shared stuff.

Initialization of console engine:

var controller = new ConsoleController();
var console = new SystemConsole(controller, new ConsoleStartConfiguration(ConsoleStartConfiguration.Colorfull)
{
	DesiredRowWidth = 128
});

ConsoleController object is responsible for many internal subsystems of Console, especially for color-matching / switching operations.

For example, one can use it to configure custom colors, for standard 16 console colors:

controller.ReplaceConsoleColors(
	new Tuple<ConsoleColor, Color>(ConsoleColor.DarkCyan, Color.Chocolate),
	new Tuple<ConsoleColor, Color>(ConsoleColor.Blue, Color.DodgerBlue),
	new Tuple<ConsoleColor, Color>(ConsoleColor.Yellow, Color.Gold),
	new Tuple<ConsoleColor, Color>(ConsoleColor.DarkBlue, Color.MidnightBlue));

ConsoleStartConfiguration object provides startup settings for Console. You could use one of the predefined sets (static properties):

  • Default – 16 colors, default start and window size,
  • Colorfull – tries 24bit color
  • Gaming – tries 24bit color, turned off buffer
  • GamingFullScreen – as above, but full-screen

You can also create your own startup where you could request specific screen / buffer „resolution” (Desired* properties), specify other monitor for fullscreen work (StartScreen property) or use one of the predefined sets with some customization, like in the sample code above. I’ve requested 128 row width to contain full line of colors in demo, by default console has 120 columns.

Currently Demo does two things:

PrintOsVersionDemo(console);
RainbowColors(console);

First displays some current system properties using some coloring and formatting helper methods:

private static void PrintOsVersionDemo(IConsole console)
{
	StatusStyles statusStyles = StatusStyles.Default;
	console.PrintStatus("Windows version", OsVersion.Info.VersionString, statusStyles, StatusStyle.Info);
	console.PrintStatus("App bitness", OsVersion.Info.ApplicationBitness, statusStyles, StatusStyle.Info);
	console.PrintStatus("OS bitness", OsVersion.Info.SystemBitness, statusStyles, StatusStyle.Info);
	console.PrintStatus("CPU bitness", OsVersion.Info.ProcessorBitness, statusStyles, StatusStyle.Info);

	console.WriteLine();

	console.PrintStatus("IsWindows10", OsVersion.Win10SystemInfo.IsWindows10, statusStyles, StatusStyle.Info);
	console.PrintStatus("IsMobile", OsVersion.Win10SystemInfo.IsMobile, statusStyles, StatusStyle.Info);
	console.PrintStatus("HasAnniversaryUpdate", OsVersion.Win10SystemInfo.HasAnniversaryUpdate, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasAnniversaryUpdate));
	console.PrintStatus("HasApril2018Update", OsVersion.Win10SystemInfo.HasApril2018Update, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasApril2018Update));
	console.PrintStatus("HasCreatorsUpdate", OsVersion.Win10SystemInfo.HasCreatorsUpdate, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasCreatorsUpdate));
	console.PrintStatus("HasFallCreatorsUpdate", OsVersion.Win10SystemInfo.HasFallCreatorsUpdate, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasFallCreatorsUpdate));
	console.PrintStatus("HasNovemberUpdate", OsVersion.Win10SystemInfo.HasNovemberUpdate, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasNovemberUpdate));
	console.PrintStatus("HasRedstone5Update", OsVersion.Win10SystemInfo.HasRedstone5Update, statusStyles,
		statusStyles.SelectFlagStyle(OsVersion.Win10SystemInfo.HasRedstone5Update));
	console.PrintStatus("IsThreshold1Version", OsVersion.Win10SystemInfo.IsThreshold1Version, statusStyles,
		statusStyles.SelectFlagStyle(!OsVersion.Win10SystemInfo.IsThreshold1Version));

	console.WaitForNextPage();
}

Second one draws some rainbow demonstration:

// based on https://github.com/bitcrazed/24bit-color/blob/master/24-bit-color.sh
private static void RainbowColors(SystemConsole console)
{
	Color foreColor = Color.White;
	Color bgColor = Color.Black;

	console.WriteLine(@"Based on: https://github.com/bitcrazed/24bit-color/blob/master/24-bit-color.sh");
	NextLine(console);

	foreach (int r in Enumerable.Range(0, 127))
	{
		console.SetColors(foreColor, Color.FromArgb(r, 0, 0));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int r in Enumerable.Range(128, 127).Reverse())
	{
		console.SetColors(foreColor, Color.FromArgb(r, 0, 0));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int g in Enumerable.Range(0, 127))
	{
		console.SetColors(foreColor, Color.FromArgb(0, g, 0));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int g in Enumerable.Range(128, 127).Reverse())
	{
		console.SetColors(foreColor, Color.FromArgb(0, g, 0));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int b in Enumerable.Range(0, 127))
	{
		console.SetColors(foreColor, Color.FromArgb(0, 0, b));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int b in Enumerable.Range(128, 127).Reverse())
	{
		console.SetColors(foreColor, Color.FromArgb(0, 0, b));
		console.WriteText('_');
	}

	NextLine(console);
	NextLine(console);

	foreach (int i in Enumerable.Range(0, 127))
	{
		console.SetColors(foreColor, BuildRainbowColor(i));
		console.WriteText('_');
	}

	NextLine(console);

	foreach (int i in Enumerable.Range(128, 127).Reverse())
	{
		console.SetColors(foreColor, BuildRainbowColor(i));
		console.WriteText('_');
	}

	NextLine(console);


	console.WaitForNextPage();
}

private static void NextLine(SystemConsole console)
{
	Color foreColor = Color.White;
	Color bgColor = Color.Black;

	console.SetColors(foreColor, bgColor);
	console.WriteLine();
}

private static Color BuildRainbowColor(int param1)
{
	int h = param1 / 43;
	int f = param1 - 43 * h;
	int t = f * 255 / 43;
	int q = 255 - t;

	switch (h)
	{
		case 0: return Color.FromArgb(255, t, 0);
		case 1: return Color.FromArgb(q, 255, 0);
		case 2: return Color.FromArgb(0, 255, t);
		case 3: return Color.FromArgb(0, q, 255);
		case 4: return Color.FromArgb(t, 0, 255);
		case 5: return Color.FromArgb(255, 0, q);

		default: throw new InvalidOperationException(@"should never reach here");
	}
}

Then depending whether 24-bit colors are supported by windows system or not (and were requested during console initialization) one can see one of the following results:

Some extra quick notes regarding usage of the library, especially components form other projects from solution:

  • Do not use any Virtual Console sequences directly as it might affect all text related algorithms.
  • Do not use directly system Console class, especially when multihreading and changing colors a lot – results might be unpredictable. There is a dedicated wrapper for multithreading operations AtomicConsole in Shared library.
  • There is dedicated BRANCH for porting colorfull.console library. Yet I’m not going to work on that as this is minor usage for me at the point. Join the project if you wish and do this ;-).
  • Gaming / fullscreen capabilities might evolve in the future (OnResize event perhaps?) as soon as I start working on it and will have better defined requirements and usage scenarios. Now it’s not tested very much.
  • Library operations needs a lot of reworking – more components, better code, some fixes to misbehaving classes, refactorings. Will restart working on this soon. But API might evolve.
  • Library for CommandLine has a lot of great (working) functionality, but still requires a lot of polishing and refactoring – venture only for your own risk 😉

Also, on Github there are available sources, samples and tutorials for older version of my libraries. They are divided into several parts:
Core / Root part – i renamed „Core” to avoid ambiguity with .Net Core
Operations
Commands engine

How do you like it? Please let me know. Join the project or raise issues directly in Github.
Cheers.