bonuska1/App/UI/TextDisplay.cs
2025-03-23 22:29:28 +03:00

310 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using static Game.I18n;
using System.Runtime.InteropServices;
using System.Text;
namespace Game.UI {
public class TextDisplay {
private const string RGB_FG_SEQ_START = "\e[38;2;";
private const string RGB_BG_SEQ_START = "\e[48;2;";
private const string ESC_SEQ_START = "\e[";
private const string SETCURSOR_END = "H";
private const string GRAPHICS_SEQ_END = "m";
private const string HIDECURSOR_SEQ = "\e[?25l";
private const string SHOWCURSOR_SEQ = "\e[?25h";
private readonly char[] SIMPLE_BOX_CHARS = { '─', '│', '┌', '┐', '└', '┘' };
private readonly char[] DOUBLE_BOX_CHARS = { '═', '║', '╔', '╗', '╚', '╝' };
private readonly char[] FALLBACK_BOX_CHARS = { '-', '|', '+', '+', '+', '+' };
private int currentFgColor = 0xffffff, currentBgColor = 0;
private int cursorRow = 0, cursorCol = 0;
private int windowWidth = 0, windowHeight = 0;
public bool InitWindow() {
if (Console.IsOutputRedirected) {
Console.WriteLine(GetMessage(Message.OUTPUT_IS_NOT_TERMINAL));
return false;
}
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
IntPtr consoleInHandle = Win32Imports.GetStdHandle(Win32Imports.STD_INPUT_HANDLE);
IntPtr consoleOutHandle = Win32Imports.GetStdHandle(Win32Imports.STD_OUTPUT_HANDLE);
uint inConsoleMode, outConsoleMode;
if (!Win32Imports.GetConsoleMode(consoleInHandle, out inConsoleMode)) {
Console.WriteLine(GetMessage(Message.ERROR_GETCONSOLEMODE));
return false;
}
if (!Win32Imports.GetConsoleMode(consoleOutHandle, out outConsoleMode)) {
Console.WriteLine(GetMessage(Message.ERROR_GETCONSOLEMODE));
return false;
}
inConsoleMode |= Win32Imports.ENABLE_VIRTUAL_TERMINAL_INPUT;
//inConsoleMode &= ~Win32Imports.ENABLE_LINE_INPUT;
outConsoleMode |= Win32Imports.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!Win32Imports.SetConsoleMode(consoleInHandle, inConsoleMode)) {
Console.WriteLine(GetMessage(Message.ERROR_SETCONSOLEMODE));
return false;
}
if (!Win32Imports.SetConsoleMode(consoleOutHandle, outConsoleMode)) {
Console.WriteLine(GetMessage(Message.ERROR_SETCONSOLEMODE));
return false;
}
SetColors(currentFgColor, currentBgColor);
HideCursor();
SetCursor(cursorRow, cursorCol);
windowWidth = Console.BufferWidth;
windowHeight = Console.BufferHeight;
return true;
}
public void ResetWindow() {
SetColors(0xffffff, 0);
SetCursor(1, 1);
ShowCursor();
}
public void PrintSimpleWelcome() {
WriteColored("Welcome to КТО ХОЧЕТ СТАТЬ СЕКУНДОМЕРОМ", 0x5e61ed);
}
public void ShowSplash() {
ClearWithColor(0x5e61ed);
DrawTitle(windowHeight / 2 + 1 - 3, 0x5e61ed);
DrawCopyright(0x5e61ed);
}
public void ShowFatalError(string text) {
ClearWithColor(0xfc5d2d);
DrawTextBox(HAlignment.Center, VAlignment.Center, BoxType.Double, windowHeight / 2 + 1, windowWidth / 2 + 1, text, 0xffffff, 0xffffff, 0xfc5d2d, windowWidth / 2);
}
public void ShowMainGameScreenBase() {
ClearWithColor(0x5e61ed);
DrawTitle(2, 0x5e61ed);
DrawCopyright(0x5e61ed);
}
public void DrawRules() {
DrawTextBox(HAlignment.Center, VAlignment.Center, BoxType.Simple, windowHeight / 2 + 1, windowWidth / 2 + 1, GetMessage(Message.RULES), 0x00fcdb, 0x00fcdb, 0x000000, windowWidth * 3 / 4);
}
void DrawTitle(int y, int bgColor) {
string text1 = GetMessage(Message.TITLE_LINE1)!;
string text2 = GetMessage(Message.TITLE_LINE2)!;
SetColors(0xffffff, 0);
int text1Start = (windowWidth - text1.Length) / 2 + 1;
SetCursor(y, text1Start - 6);
for (int i = 0; i < 6; ++i) {
WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6));
}
WriteColored(text1, 0xffffff, 0);
for (int i = 6; i >= 0; --i) {
WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6));
}
int text2Start = (windowWidth - text2.Length) / 2 + 1;
SetCursor(y + 2, text2Start - 6);
for (int i = 0; i < 6; ++i) {
WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6));
}
WriteColored(text2, 0xffffff, 0);
for (int i = 6; i >= 0; --i) {
WriteColored(" ", -1, ColorBlend(0, bgColor, (float)i / 6));
}
}
void DrawCopyright(int bgColor) {
string bottomText = "(c) subvia, 2025";
SetCursor(windowHeight, (windowWidth - bottomText.Length) / 2 + 1);
WriteColored(bottomText, 0x4d4dc0, bgColor);
}
void DrawTextBox(HAlignment hAlign, VAlignment vAlign, BoxType boxType, int y, int x, string text, int borderColor, int textColor, int bgColor = -1, int preferredWidth = -1) {
int maxWidth = windowWidth;
switch (hAlign) {
case HAlignment.Left:
maxWidth = windowWidth - x + 1;
break;
case HAlignment.Center:
maxWidth = Math.Min(x - 1, windowWidth - x + 1) * 2;
break;
case HAlignment.Right:
maxWidth = x - 1;
break;
}
int maxHeight = windowHeight;
switch (vAlign) {
case VAlignment.Top:
maxHeight = windowHeight - y + 1;
break;
case VAlignment.Center:
maxHeight = Math.Min(y - 1, windowHeight - y + 1) * 2;
break;
case VAlignment.Bottom:
maxHeight = y - 1;
break;
}
string[] lines = WordWrap(text, (preferredWidth == -1 ? maxWidth : preferredWidth) - 4, out bool overflow);
bool reflowed = false;
if (preferredWidth != -1 && lines.Length > maxHeight - 4) {
reflowed = true;
lines = WordWrap(text, maxWidth - 4, out overflow);
}
int actualWidth = Math.Min(preferredWidth == -1 ? maxWidth : (reflowed ? maxWidth : preferredWidth), lines.Max(s => s.Length) + 4);
int actualHeight = Math.Min(maxHeight, lines.Length + 4);
int left = hAlign == HAlignment.Left ? x : (hAlign == HAlignment.Right ? x - actualWidth + 1 : x - (actualWidth - 1) / 2);
int top = vAlign == VAlignment.Top ? y : (vAlign == VAlignment.Bottom ? y - actualHeight + 1 : y - (actualHeight - 1) / 2);
DrawBox(boxType, top, left, actualHeight, actualWidth, borderColor, bgColor);
SetColors(textColor, -1);
for (int i = 0; i < actualHeight - 4; ++i) {
SetCursor(top + 2 + i, left + 2);
WriteColored(lines[i], textColor, bgColor);
}
}
public void DrawBox(BoxType boxType, int y, int x, int h, int w, int borderColor, int bgColor = -1) {
char[] boxChars = boxType == BoxType.Simple ? SIMPLE_BOX_CHARS : (boxType == BoxType.Double ? DOUBLE_BOX_CHARS : FALLBACK_BOX_CHARS);
SetColors(borderColor, bgColor);
SetCursor(y, x);
StringBuilder output = new StringBuilder();
output.Append(boxChars[2]);
output.Append(boxChars[0], w - 2);
output.Append(boxChars[3]);
WriteColored(output.ToString(), borderColor, bgColor);
for (int i = 0; i < h - 2; ++i) {
output.Clear();
SetCursor(y + 1 + i, x);
output.Append(boxChars[1]);
output.Append(' ', w - 2);
output.Append(boxChars[1]);
WriteColored(output.ToString(), borderColor, bgColor);
}
SetCursor(y + h - 1, x);
output.Clear();
output.Append(boxChars[4]);
output.Append(boxChars[0], w - 2);
output.Append(boxChars[5]);
WriteColored(output.ToString(), borderColor, bgColor);
}
public void WriteColored(string s, int color, int bgColor = -1) {
if (color == -1) color = currentFgColor;
(int r, int g, int b) = ((color >> 16) & 255, (color >> 8) & 255, color & 255);
currentFgColor = color;
if (bgColor == -1) {
Console.Write(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END + s);
} else {
(int br, int bg, int bb) = ((bgColor >> 16) & 255, (bgColor >> 8) & 255, bgColor & 255);
Console.Write(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END + RGB_BG_SEQ_START + br + ';' + bg + ';' + bb + GRAPHICS_SEQ_END + s);
currentBgColor = bgColor;
}
}
public void SetColors(int fg, int bg) {
StringBuilder command = new StringBuilder();
if (fg != -1) {
(int r, int g, int b) = ((fg >> 16) & 255, (fg >> 8) & 255, fg & 255);
command.Append(RGB_FG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END);
currentFgColor = fg;
}
if (bg != -1) {
(int r, int g, int b) = ((bg >> 16) & 255, (bg >> 8) & 255, bg & 255);
command.Append(RGB_BG_SEQ_START + r + ';' + g + ';' + b + GRAPHICS_SEQ_END);
currentBgColor = bg;
}
Console.Write(command.ToString());
}
public void ClearWithColor(int color) {
(int r, int g, int b) = ((color >> 16) & 255, (color >> 8) & 255, color & 255);
SetColors(-1, color);
Console.Write("\e[2J");
}
public void SetCursor(int row, int column) {
if (cursorRow == row && cursorCol == column) return;
Console.Write(ESC_SEQ_START + row + ';' + column + SETCURSOR_END);
cursorRow = row;
cursorCol = column;
}
public void HideCursor() {
Console.Write(HIDECURSOR_SEQ);
}
public void ShowCursor() {
Console.Write(SHOWCURSOR_SEQ);
}
public int ColorBlend(int fg, int bg, float alpha) {
//return (int)(fg * alpha + bg * (1f - alpha));
return ((int)(((fg >> 16) & 255) * alpha + ((bg >> 16) & 255) * (1 - alpha)) << 16) + ((int)(((fg >> 8) & 255) * alpha + ((bg >> 8) & 255) * (1 - alpha)) << 8) + (int)((fg & 255) * alpha + (bg & 255) * (1 - alpha));
}
public string[] WordWrap(string text, int width, out bool overflow) {
overflow = false;
List<string> lines = new List<string>();
StringBuilder line = new StringBuilder(text.Length / width * 4 / 3);
int remaining = width;
foreach (string word in text.Split(new char[] {' ', '\n'}, StringSplitOptions.RemoveEmptyEntries)) {
if (word.Length <= remaining) {
if (line.Length == 0 || line[^1] != ' ') {
line.Append(' ');
remaining -= 1;
}
line.Append(word);
remaining -= word.Length;
} else {
lines.Add(line.ToString());
line.Clear();
line.Append(word);
remaining = width - word.Length;
if (remaining < 0) {
overflow = true;
line.Remove(width + 1, line.Length - (width + 1));
lines.Add(line.ToString());
line.Clear();
}
}
}
if (line.Length > 0)
lines.Add(line.ToString());
return lines.ToArray();
}
public enum HAlignment {
Left, Center, Right
}
public enum VAlignment {
Top, Center, Bottom
}
public enum BoxType {
Simple,
Double,
Fallback,
}
}
static class Win32Imports
{
public const int STD_INPUT_HANDLE = -10;
public const int STD_OUTPUT_HANDLE = -11;
public const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
public const uint ENABLE_LINE_INPUT = 2;
public const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int stdHandle);
[DllImport("kernel32.dll")]
public static extern bool SetConsoleMode(IntPtr consoleHandle, uint mode);
[DllImport("kernel32.dll")]
public static extern bool GetConsoleMode(IntPtr consoleHandle, out uint mode);
}
}