diff --git a/Redcraft.Utility/Source/Private/Miscellaneous/Console.cpp b/Redcraft.Utility/Source/Private/Miscellaneous/Console.cpp new file mode 100644 index 0000000..a47c264 --- /dev/null +++ b/Redcraft.Utility/Source/Private/Miscellaneous/Console.cpp @@ -0,0 +1,462 @@ +#include "Miscellaneous/Console.h" + +#include + +#if PLATFORM_WINDOWS +# undef TEXT +# include +#elif PLATFORM_LINUX +# include +# include +#endif + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +#if PLATFORM_WINDOWS + +NODISCARD bool InitANSIConsole() +{ + static bool bResult = [] + { + HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return false; + + DWORD ConsoleMode = 0; + + if (!GetConsoleMode(Console, &ConsoleMode)) return false; + + ConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + if (!SetConsoleMode(Console, ConsoleMode)) return false; + + return true; + } + (); + + return bResult; +} + +#endif + +EColor GForegroundColor = EColor::Default; +EColor GBackgroundColor = EColor::Default; + +EColor GetForegroundColor() +{ +# if PLATFORM_WINDOWS + { + if (InitANSIConsole()) return GForegroundColor; + + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return EColor::Default; + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return EColor::Default; + + const WORD Color = ConsoleInfo.wAttributes; + + EColor Result = EColor::Black; + + if (Color & FOREGROUND_RED) Result |= EColor::Red; + if (Color & FOREGROUND_GREEN) Result |= EColor::Green; + if (Color & FOREGROUND_BLUE) Result |= EColor::Blue; + if (Color & FOREGROUND_INTENSITY) Result |= EColor::Intensity; + + return Result; + } +# endif + + return GForegroundColor; +} + +EColor GetBackgroundColor() +{ +# if PLATFORM_WINDOWS + { + if (InitANSIConsole()) return GBackgroundColor; + + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return EColor::Default; + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return EColor::Default; + + const WORD Color = ConsoleInfo.wAttributes; + + EColor Result = EColor::Black; + + if (Color & BACKGROUND_RED) Result |= EColor::Red; + if (Color & BACKGROUND_GREEN) Result |= EColor::Green; + if (Color & BACKGROUND_BLUE) Result |= EColor::Blue; + if (Color & BACKGROUND_INTENSITY) Result |= EColor::Intensity; + + return Result; + } +# endif + + return GBackgroundColor; +} + +EColor SetForegroundColor(EColor InColor) +{ + if (IsOutputRedirected()) return GetForegroundColor(); + +# if PLATFORM_WINDOWS + { + if (!InitANSIConsole()) + { + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return GetForegroundColor(); + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return GetForegroundColor(); + + WORD Color = ConsoleInfo.wAttributes & ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); + + if (InColor == EColor::Default) InColor = EColor::White; + + if (!!(InColor & EColor::Red)) Color |= FOREGROUND_RED; + if (!!(InColor & EColor::Green)) Color |= FOREGROUND_GREEN; + if (!!(InColor & EColor::Blue)) Color |= FOREGROUND_BLUE; + if (!!(InColor & EColor::Intensity)) Color |= FOREGROUND_INTENSITY; + + if (!SetConsoleTextAttribute(Console, Color)) return GetForegroundColor(); + + return InColor; + } + } +# endif + +# if PLATFORM_WINDOWS || PLATFORM_LINUX + { + int Result; + + switch (InColor) + { + case EColor::Black: Result = std::fputs("\033[30m", stdout); break; + case EColor::Red: Result = std::fputs("\033[31m", stdout); break; + case EColor::Green: Result = std::fputs("\033[32m", stdout); break; + case EColor::Yellow: Result = std::fputs("\033[33m", stdout); break; + case EColor::Blue: Result = std::fputs("\033[34m", stdout); break; + case EColor::Magenta: Result = std::fputs("\033[35m", stdout); break; + case EColor::Cyan: Result = std::fputs("\033[36m", stdout); break; + case EColor::White: Result = std::fputs("\033[37m", stdout); break; + case EColor::BrightBlack: Result = std::fputs("\033[90m", stdout); break; + case EColor::BrightRed: Result = std::fputs("\033[91m", stdout); break; + case EColor::BrightGreen: Result = std::fputs("\033[92m", stdout); break; + case EColor::BrightYellow: Result = std::fputs("\033[93m", stdout); break; + case EColor::BrightBlue: Result = std::fputs("\033[94m", stdout); break; + case EColor::BrightMagenta: Result = std::fputs("\033[95m", stdout); break; + case EColor::BrightCyan: Result = std::fputs("\033[96m", stdout); break; + case EColor::BrightWhite: Result = std::fputs("\033[97m", stdout); break; + default: Result = std::fputs("\033[39m", stdout); break; + } + + if (Result == EOF) return GetForegroundColor(); + + return GForegroundColor = InColor; + } +# endif + + return GetForegroundColor(); +} + +EColor SetBackgroundColor(EColor InColor) +{ + if (IsOutputRedirected()) return GetBackgroundColor(); + +# if PLATFORM_WINDOWS + { + if (!InitANSIConsole()) + { + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return GetBackgroundColor(); + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return GetBackgroundColor(); + + WORD Color = ConsoleInfo.wAttributes & ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); + + if (InColor == EColor::Default) InColor = EColor::Black; + + if (!!(InColor & EColor::Red)) Color |= BACKGROUND_RED; + if (!!(InColor & EColor::Green)) Color |= BACKGROUND_GREEN; + if (!!(InColor & EColor::Blue)) Color |= BACKGROUND_BLUE; + if (!!(InColor & EColor::Intensity)) Color |= BACKGROUND_INTENSITY; + + if (!SetConsoleTextAttribute(Console, Color)) return GetBackgroundColor(); + + return InColor; + } + } +# endif + +# if PLATFORM_WINDOWS || PLATFORM_LINUX + { + int Result; + + switch (InColor) + { + case EColor::Black: Result = std::fputs("\033[40m", stdout); break; + case EColor::Red: Result = std::fputs("\033[41m", stdout); break; + case EColor::Green: Result = std::fputs("\033[42m", stdout); break; + case EColor::Yellow: Result = std::fputs("\033[43m", stdout); break; + case EColor::Blue: Result = std::fputs("\033[44m", stdout); break; + case EColor::Magenta: Result = std::fputs("\033[45m", stdout); break; + case EColor::Cyan: Result = std::fputs("\033[46m", stdout); break; + case EColor::White: Result = std::fputs("\033[47m", stdout); break; + case EColor::BrightBlack: Result = std::fputs("\033[100m", stdout); break; + case EColor::BrightRed: Result = std::fputs("\033[101m", stdout); break; + case EColor::BrightGreen: Result = std::fputs("\033[102m", stdout); break; + case EColor::BrightYellow: Result = std::fputs("\033[103m", stdout); break; + case EColor::BrightBlue: Result = std::fputs("\033[104m", stdout); break; + case EColor::BrightMagenta: Result = std::fputs("\033[105m", stdout); break; + case EColor::BrightCyan: Result = std::fputs("\033[106m", stdout); break; + case EColor::BrightWhite: Result = std::fputs("\033[107m", stdout); break; + default: Result = std::fputs("\033[49m", stdout); break; + } + + if (Result == EOF) return GetBackgroundColor(); + + return GBackgroundColor = InColor; + } +# endif + + return GetBackgroundColor(); +} + +uint GetWindowWidth() +{ +# if PLATFORM_WINDOWS + { + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return static_cast(-1); + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return static_cast(-1); + + return static_cast(ConsoleInfo.srWindow.Right - ConsoleInfo.srWindow.Left + 1); + } +# elif PLATFORM_LINUX + { + winsize Size; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &Size) == -1) return static_cast(-1); + + return static_cast(Size.ws_col); + } +# endif + + return static_cast(-1); +} + +uint GetWindowHeight() +{ +# if PLATFORM_WINDOWS + { + const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return static_cast(-1); + + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + + if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return static_cast(-1); + + return static_cast(ConsoleInfo.srWindow.Bottom - ConsoleInfo.srWindow.Top + 1); + } +# elif PLATFORM_LINUX + { + winsize Size; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &Size) == -1) return static_cast(-1); + + return static_cast(Size.ws_row); + } +# endif + + return static_cast(-1); +} + +bool IsInputRedirected() +{ +# if PLATFORM_WINDOWS + { + const HANDLE StandardInput = GetStdHandle(STD_INPUT_HANDLE); + + if (StandardInput == INVALID_HANDLE_VALUE) return false; + + DWORD FileType = GetFileType(StandardInput); + + return FileType != FILE_TYPE_CHAR; + } +# elif PLATFORM_LINUX + { + return isatty(fileno(stdin)) == 0; + } +# endif + + return false; +} + +bool IsOutputRedirected() +{ +# if PLATFORM_WINDOWS + { + const HANDLE StandardOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + if (StandardOutput == INVALID_HANDLE_VALUE) return false; + + DWORD FileType = GetFileType(StandardOutput); + + return FileType != FILE_TYPE_CHAR; + } +# elif PLATFORM_LINUX + { + return isatty(fileno(stdout)) == 0; + } +# endif + + return false; +} + +bool IsErrorRedirected() +{ +# if PLATFORM_WINDOWS + { + const HANDLE StandardError = GetStdHandle(STD_ERROR_HANDLE); + + if (StandardError == INVALID_HANDLE_VALUE) return false; + + DWORD FileType = GetFileType(StandardError); + + return FileType != FILE_TYPE_CHAR; + } +# elif PLATFORM_LINUX + { + return isatty(fileno(stderr)) == 0; + } +# endif + + return false; +} + +void Clear() +{ + if (IsOutputRedirected()) return; + +# if PLATFORM_WINDOWS + { + std::system("cls"); + } +# elif PLATFORM_LINUX + { + Ignore = std::fputs("\033[2J\033[1;1H", stdout); + } +# endif +} + +char Input(bool bEcho) +{ + if (bEcho || IsOutputRedirected()) + { + const int Result = std::getchar(); + + if (Result == EOF) return static_cast(-1); + + return static_cast(Result); + } + +# if PLATFORM_WINDOWS + { + const HANDLE Console = GetStdHandle(STD_INPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return static_cast(-1); + + DWORD ConsoleMode = 0; + + GetConsoleMode(Console, &ConsoleMode); + + SetConsoleMode(Console, ConsoleMode & ~ENABLE_ECHO_INPUT); + + const char Result = Input(); + + SetConsoleMode(Console, ConsoleMode); + + return Result; + } +# elif PLATFORM_LINUX + { } +# endif + + return static_cast(-1); +} + +FString InputLn(bool bEcho) +{ + if (bEcho || IsOutputRedirected()) + { + FString Result; + + while (true) + { + const int Char = std::getchar(); + + if (Char == EOF || Char == '\n') break; + + Result.PushBack(static_cast(Char)); + } + + return Result; + } + +# if PLATFORM_WINDOWS + { + const HANDLE Console = GetStdHandle(STD_INPUT_HANDLE); + + if (Console == INVALID_HANDLE_VALUE) return ""; + + DWORD ConsoleMode = 0; + + GetConsoleMode(Console, &ConsoleMode); + + SetConsoleMode(Console, ConsoleMode & ~ENABLE_ECHO_INPUT); + + const FString Result = InputLn(); + + SetConsoleMode(Console, ConsoleMode); + + return Result; + } +# elif PLATFORM_LINUX + { } +# endif + + return ""; +} + +bool Print(char Char) +{ + return std::putchar(Char) != EOF; +} + +bool Error(char Char) +{ + return std::fputc(Char, stderr) != EOF; +} + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Miscellaneous/Console.h b/Redcraft.Utility/Source/Public/Miscellaneous/Console.h new file mode 100644 index 0000000..1aefd9e --- /dev/null +++ b/Redcraft.Utility/Source/Public/Miscellaneous/Console.h @@ -0,0 +1,217 @@ +#pragma once + +#include "CoreTypes.h" +#include "TypeTraits/TypeTraits.h" +#include "Templates/Utility.h" +#include "Iterators/Utility.h" +#include "Strings/Formatting.h" +#include "Strings/StringView.h" +#include "Strings/String.h" +#include "Miscellaneous/BitwiseEnum.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +/** An enumeration that defines the color of the console. */ +enum class EColor : uint8 +{ + Default = 0xFF, + + Black = 0b0000, + Red = 0b0001, + Green = 0b0010, + Blue = 0b0100, + Intensity = 0b1000, + + Cyan = Green | Blue, + Magenta = Blue | Red, + Yellow = Red | Green, + + White = Red | Green | Blue, + + BrightBlack = Intensity | Black, + BrightRed = Intensity | Red, + BrightGreen = Intensity | Green, + BrightBlue = Intensity | Blue, + BrightYellow = Intensity | Yellow, + BrightMagenta = Intensity | Magenta, + BrightCyan = Intensity | Cyan, + BrightWhite = Intensity | White +}; + +ENABLE_ENUM_CLASS_BITWISE_OPERATIONS(EColor) + +/** @return The color of the console. */ +NODISCARD REDCRAFTUTILITY_API EColor GetForegroundColor(); +NODISCARD REDCRAFTUTILITY_API EColor GetBackgroundColor(); + +/** Set the color of the console. Returns the color that was successfully set. */ +REDCRAFTUTILITY_API EColor SetForegroundColor(EColor InColor); +REDCRAFTUTILITY_API EColor SetBackgroundColor(EColor InColor); + +/** @return The size of the console window. */ +NODISCARD REDCRAFTUTILITY_API uint GetWindowWidth(); +NODISCARD REDCRAFTUTILITY_API uint GetWindowHeight(); + +/** @return true if the standard stream is redirected. */ +NODISCARD REDCRAFTUTILITY_API bool IsInputRedirected(); +NODISCARD REDCRAFTUTILITY_API bool IsOutputRedirected(); +NODISCARD REDCRAFTUTILITY_API bool IsErrorRedirected(); + +/** Clear the console screen. */ +REDCRAFTUTILITY_API void Clear(); + +/** @return The input character from the standard input. */ +NODISCARD REDCRAFTUTILITY_API char Input(bool bEcho = true); + +/** @return The input line from the standard input. */ +NODISCARD REDCRAFTUTILITY_API FString InputLn(bool bEcho = true); + +/** Print the character to the standard output. */ +REDCRAFTUTILITY_API bool Print(char Char); + +/** Print the formatted string to the standard output. */ +template +FORCEINLINE bool Print(FStringView Fmt, Ts&&... Args) +{ + struct FStandardOutputIterator + { + FORCEINLINE constexpr FStandardOutputIterator& operator=(char Char) + { + bError |= !Print(Char); + return *this; + } + + FORCEINLINE constexpr bool operator==(FDefaultSentinel) const { return bError; } + + FORCEINLINE constexpr FStandardOutputIterator& operator*() { return *this; } + FORCEINLINE constexpr FStandardOutputIterator& operator++() { return *this; } + FORCEINLINE constexpr FStandardOutputIterator& operator++(int) { return *this; } + + bool bError = false; + }; + + static_assert(COutputIterator); + + FStandardOutputIterator Iter; + + auto Range = Ranges::View(Iter, DefaultSentinel); + + Iter = Algorithms::Format(Range, Fmt, Forward(Args)...); + + return Iter != DefaultSentinel; +} + +/** Print the value to the standard output. */ +template +FORCEINLINE bool Print(T&& Value) +{ + if constexpr (CSameAs, char>) + { + return Print(static_cast(Value)); + } + + else if constexpr (CConvertibleTo) + { + return Print(static_cast(Value)); + } + + else return Print(TEXT("{0}"), Forward(Value)); +} + +/** Print the newline character to the standard output. */ +FORCEINLINE bool PrintLn() +{ + return Print(TEXT("\n")); +} + +/** Print the string to the standard output and append the newline character. */ +template +FORCEINLINE bool PrintLn(FStringView Fmt, Ts&&... Args) +{ + return Print(Fmt, Forward(Args)...) && Print(TEXT("\n")); +} + +/** Print the value to the standard output and append the newline character. */ +template +FORCEINLINE bool PrintLn(T&& Value) +{ + return Print(Forward(Value)) && Print(TEXT("\n")); +} + +/** Print the character to the standard error. */ +REDCRAFTUTILITY_API bool Error(char Char); + +/** Print the formatted string to the standard error. */ +template +FORCEINLINE bool Error(FStringView Fmt, Ts&&... Args) +{ + struct FStandardOutputIterator + { + FORCEINLINE constexpr FStandardOutputIterator& operator=(char Char) + { + bError |= !Error(Char); + return *this; + } + + FORCEINLINE constexpr bool operator==(FDefaultSentinel) const { return bError; } + + FORCEINLINE constexpr FStandardOutputIterator& operator*() { return *this; } + FORCEINLINE constexpr FStandardOutputIterator& operator++() { return *this; } + FORCEINLINE constexpr FStandardOutputIterator& operator++(int) { return *this; } + + bool bError = false; + }; + + static_assert(COutputIterator); + + FStandardOutputIterator Iter; + + auto Range = Ranges::View(Iter, DefaultSentinel); + + Iter = Algorithms::Format(Range, Fmt, Forward(Args)...); + + return Iter != DefaultSentinel; +} + +/** Print the value to the standard error. */ +template +FORCEINLINE bool Error(T&& Value) +{ + if constexpr (CSameAs, char>) + { + return Error(static_cast(Value)); + } + + else if constexpr (CConvertibleTo) + { + return Error(static_cast(Value)); + } + + else return Error(TEXT("{0}"), Forward(Value)); +} + +/** Print the newline character to the standard error. */ +FORCEINLINE bool ErrorLn() +{ + return Error(TEXT("\n")); +} + +/** Print the string to the standard error and append the newline character. */ +template +FORCEINLINE bool ErrorLn(FStringView Fmt, Ts&&... Args) +{ + return Error(Fmt, Forward(Args)...) && Error(TEXT("\n")); +} + +/** Print the value to the standard error and append the newline character. */ +template +FORCEINLINE bool ErrorLn(T&& Value) +{ + return Error(Forward(Value)) && Error(TEXT("\n")); +} + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END