goblinfactory / konsole

Home of the simple console library consisting of ProgressBar, Window, Form, Draw & MockConsole (C# console progress bar with support for single or multithreaded progress updates) Window is a 100%-ish console compatible window, supporting all normal console writing to a windowed section of the screen, supporting scrolling and clipping of console output.
719 stars 62 forks source link

Full Color Theming support #63

Open Unknown6656 opened 4 years ago

Unknown6656 commented 4 years ago

Hi, I just wanted to leave the comment that C# supports 24Bit colors for the Windows console. You can find an example in one of my libraries:

Color Setter Functions

public static class ConsoleExtensions
{
    public static RGBAColor ForegroundColor
    {
        set => Console.Write(value.ToVT100ForegroundString());
    }

    public static RGBAColor BackgroundColor
    {
        set => Console.Write(value.ToVT100BackgroundString());
    }

    // this static constructor is needed on non-unix machines, as the VT100 color codes are not always enabled by default.
    // see https://github.com/Unknown6656/Unknown6656.Core/blob/master/Unknown6656.Core/Controls/Console/ConsoleExtensions.cs
    static ConsoleExtensions()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            STDINConsoleMode |= ConsoleMode.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
            STDOUTConsoleMode |= ConsoleMode.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
        }
    }
}

https://github.com/Unknown6656/Unknown6656.Core/blob/master/Unknown6656.Core/Controls/Console/ConsoleExtensions.cs#L37..L57

Color Data Structure

public struct RGBAColor
{
    public byte B,G,B,A;

    public RGBAColor(uint argb)
        : this()
    {
        if (argb <= 0xffff)
            argb = ((argb & 0xf000) << 16)
                 | ((argb & 0xff00) << 12)
                 | ((argb & 0x0ff0) << 8)
                 | ((argb & 0x00ff) << 4)
                 | (argb & 0x000f);

        A = (byte)((argb >> 24) & 0xff);
        R = (byte)((argb >> 16) & 0xff);
        G = (byte)((argb >> 8) & 0xff);
        B = (byte)(argb & 0xff);
    }

    public string ToVT100ForegroundString() => $"\x1b[38;2;{R};{G};{B}m";

    public string ToVT100BackgroundString() => $"\x1b[48;2;{R};{G};{B}m";

    public static implicit operator RGBAColor(uint argb) => new RGBAColor(argb);

    public static implicit operator RGBAColor(ConsoleColor color) => color_scheme[color];

    private static readonly Dictionary<ConsoleColor, RGBAColor> color_scheme = new()
    {
        [ConsoleColor.Black      ] = 0xff000000,
        [ConsoleColor.DarkBlue   ] = 0xff000080,
        [ConsoleColor.DarkGreen  ] = 0xff008000,
        [ConsoleColor.DarkCyan   ] = 0xff008080,
        [ConsoleColor.DarkRed    ] = 0xff800000,
        [ConsoleColor.DarkMagenta] = 0xff800080,
        [ConsoleColor.DarkYellow ] = 0xff808000,
        [ConsoleColor.Gray       ] = 0xffc0c0c0,
        [ConsoleColor.DarkGray   ] = 0xff808080,
        [ConsoleColor.Blue       ] = 0xff0000ff,
        [ConsoleColor.Green      ] = 0xff00ff00,
        [ConsoleColor.Cyan       ] = 0xff00ffff,
        [ConsoleColor.Red        ] = 0xffff0000,
        [ConsoleColor.Magenta    ] = 0xffff00ff,
        [ConsoleColor.Yellow     ] = 0xffffff00,
        [ConsoleColor.White      ] = 0xffffffff,
    };
}

https://github.com/Unknown6656/Unknown6656.Core/blob/master/Unknown6656.Core/Imaging/RGBAColor.cs

Usage

public void Main()
{
    ConsoleExtensions.ForegroundColor = 0xfe80; // set color using hex literals.
    ConsoleExtensions.BackgroundColor = ConsoleColor.Blue; // traditional method.

    Console.WriteLine("Hello World!");
    Console.WriteLine("\x1b[4mHello World!\x1b[24m"); // this is underlined text using VT100-commands!
}

Maybe this feature could be handy for you...

goblinfactory commented 3 years ago

Hell yeah! nice one....thank you. I need to fix performance first, then this will absolutely be next on my backlog to add.

Unknown6656 commented 3 years ago

Sure thing, take your time :)

goblinfactory commented 3 years ago

(random thoughts on VT100 terminal support, and 24 Bit colour support)

I think the plan moving forward will be to use custom high speed writers per platform to provide 24Bit colour support, bypassing the need for terminal emulation support. It will mean that we won't support terminal codes, and I'm ok with that. I'm not trying to build a terminal, I'm trying to build a library that gives users a simple way to build stuff. at a later stage, if terminal emulation support is really a big thing, we can add it in, and a user could nominate a particular console to be terminal Foo/VT100 etc compatible, and have some running process, e.g. maybe a tail logger, or some other redirected output be streamed to that window.

The reason for this, is that Konsole essentially provides a virtual abstraction over the native OS co-ordinate system, as well as an abstraction over locking that allows users to simulate global state changes "per region" of the screen, and have multiple threads write to different regions. This is currently achieved by setting, and resetting all global state, foreground color, background colour, currentX, currentY plus anything else on every write to the screen, often for just a single character. The OS support for making these state changes is very slow, and layering control abstractions, like tables and lists on top of this abstraction results in a visibly slow draw.

My focus now is addressing performance, to make Konsole fast enough for serious application development. I'll be able to bake in the colour support to high speed writers without needing control characters, since the high speed writers each use native OS rendering methods, specific to each OS.

mmm, just realised, I can easily add specific support for specific konsole commands, e.g. have a new VT100Konsole(); taht will parse text being written for specific commands, and then break up the writes accordingly. Each base class Konsole window manages it's own state, including current fore and backround colour so parsing VT100 commands, and replacing with Goblinfactory.Konsole state changes should be straight forward enough, supporting only the critical command necessary to be able to consume redirected output from something that is expecting to be run inside a specific terminal. e.g. Blink would not be worth the hassle to support etc. If there are other commands like Double Height or Double width, or Bold, then I would not bother supporting those, other than for bold, being able to specify a colour combination that will be used as a replacement for bold characters. (same pattern for strikethrough etc)

goblinfactory commented 3 years ago

thanks for the nudge on 24bit colour, appreciated :D

Unknown6656 commented 3 years ago

Yes, I can understand your design choises; they are completely sensible. I opened this discussion less because I wanted a new feature or raise an issue ... it was rather thought of as a constructive input for the future :)

I have currently not yet used Konsole before -- I stumbled on this project by accident, but I intend to use it in the future. I've written some "console-based UI" libraries before, but they always had a horrible performance, so I usually gave up after a certain time.

I also have an other idea for bold/fat/italic/... characters: It may be possible to use special unicode section, e.g. as shown by this unicode text conversion. I know that linux/bash can process and display these chars - but I do not know whether that is something relevant to your project (and I do not know whether the windows console would be able to display those characters). Screenshot from 2020-11-14 16-10-59

goblinfactory commented 3 years ago

Nice! appreciate the ideas...alway great to be able to interact with potential library users. When do you start using Konsole, please drop me feedback on how you're getting along. Cheers, Alan

Unknown6656 commented 3 years ago

Well, I'm always happy to propose potential features (even though I know that they can be a lot of work sometimes). I'm currently thinking about transitioning some older projects away from my own libraries towards Konsole. Right now, I'm still busy with my university thesis, however, I'll provide you some feedback as soon as possible!

Cheers!

agbarbosa commented 3 years ago

Can I also add (I don´t know if it already exists) the Split (SplitRows and SplitColumns) can have its own background color and also a way to remove the Frame line?

goblinfactory commented 3 years ago

Some RGB color support is coming, plus some big customisations for "styling", see this discussion here ... https://gitter.im/goblinfactory-konsole/community themed-dirlist

see this discussion for limitations on styling, specifically if you want to run within a windows console, there are only 16 colors allowed in a pallet at any time : https://stackoverflow.com/questions/22196664/use-custom-console-colors

The hack is to change colouring each time you change different types of fullscreen form.

goblinfactory commented 3 years ago

also ... present for you, .. .this is already in the current version of Konsole. Let me know if you have any issues setting or removing the frame line? Screen Shot 2021-01-19 at 19 28 34

goblinfactory commented 3 years ago

background color for splitleft, is coming in a next release. next release supports "themes", and you can split a window with a new theme. Here's one of the classes that does splitting, LeftRight in the feature branch that implements that ...if you want a sneak preview? https://github.com/goblinfactory/konsole/blob/feature/list-view-themes-no-tuples-docmd/src/Konsole/Layouts/SplitLeftRightExtensions.cs extract

        public static (IConsole left, IConsole right) SplitLeftRight(this IConsole c, string leftTitle, string rightTitle, BorderCollapse border = Collapse)
        {
            return _SplitLeftRight(c, leftTitle, rightTitle, LineThickNess.Single, border, c.ForegroundColor, c.BackgroundColor);
        }

        public static (IConsole left, IConsole right) SplitLeftRight(this IConsole c, string leftTitle, string rightTitle, ConsoleColor foreground, ConsoleColor background, BorderCollapse border = Collapse)
        {
            return _SplitLeftRight(c, leftTitle, rightTitle, LineThickNess.Single, border, foreground, background);
        }

        public static (IConsole left, IConsole right) SplitLeftRight(this IConsole c, string leftTitle, string rightTitle, LineThickNess thickness, BorderCollapse border = Collapse)
        {
            return _SplitLeftRight(c, leftTitle, rightTitle, thickness, border, c.ForegroundColor, c.BackgroundColor);
        }

        public static (IConsole left, IConsole right) SplitLeftRight(this IConsole c, string leftTitle, string rightTitle, LineThickNess thickness, ConsoleColor foreground, ConsoleColor background, BorderCollapse border = Collapse)
        {
            return _SplitLeftRight(c, leftTitle, rightTitle, thickness, border, foreground, background);
        }
agbarbosa commented 3 years ago

also ... present for you, .. .this is already in the current version of Konsole. Let me know if you have any issues setting or removing the frame line? Screen Shot 2021-01-19 at 19 28 34

What is the Thikness selection for "none". I could only find Single or Double.

goblinfactory commented 3 years ago

Nooo! doh.... you're so right! sorry...

Just realised I changed this in the new branch coming soon, ...there will be a setting blankChar, and there are ways to split without a border. I forgot it was in the new branch, ... it's been a while

https://github.com/goblinfactory/konsole/blob/feature/list-view-themes-no-tuples-docmd/src/Konsole/Drawing/LineThickNess.cs

goblinfactory commented 3 years ago

definately, being able to split without borders, or even blank border will be required. please raise a new issue for that and I'll make sure it's in the next release.

goblinfactory commented 3 years ago

Screen Shot 2021-02-09 at 15 57 35 @agbarbosa I'm busy with the changes for split with no border now. it's quite a big change since there's a lot of edge cases and lots of overloads to change, will release an alpha to start testing/playing with shortly.

goblinfactory commented 3 years ago

unofficial 7.0.0.1-alpha package is already released to Nuget to play with. It doesn't contain the split without border, but was necessary to complete before being able to make this change. Working on it now.

goblinfactory commented 3 years ago

FYI: In case it's helpful you can already use SplitLeft() and SplitRight() to split a window without a border, provided you are happy with the window being split 50/50. The following has been working for a while in Konsole 6.2 and below.

            [Test]
            [TestCase(1, 19)]
            [TestCase(2, 20)]
            [TestCase(3, 21)]
            public void LeftHalf_and_RightHalf_WithoutBorder_ShouldFillTheParentConsole(int test, int width)
            {
                // test to show how uneven lines are split between left and right windows.
                var c = new MockConsole(width, 5);
                var left = c.SplitLeft();
                var right = c.SplitRight();
                left.WriteLine("one");
                left.WriteLine("two");
                left.WriteLine("three");

                right.WriteLine("four");
                right.WriteLine("five");
                right.Write("six");
                Console.WriteLine(c.BufferString);

                var _19Cols = new[]
                {
                    "one      four      ",
                    "two      five      ",
                    "three    six       ",
                    "                   ",
                    "                   ",
            };

                var _20Cols = new[]
                {
                    "one       four      ",
                    "two       five      ",
                    "three     six       ",
                    "                    ",
                    "                    "
            };

                var _21Cols = new[]
                {
                    "one       four       ",
                    "two       five       ",
                    "three     six        ",
                    "                     ",
                    "                     ",

            };

                var expecteds = new[]
                {
                _19Cols, _20Cols, _21Cols
            };
                c.Buffer.ShouldBe(expecteds[test - 1]);
            }
goblinfactory commented 3 years ago

Screen Shot 2021-02-09 at 16 14 49