diff --git a/App/UI/TextDisplay.cs b/App/UI/TextDisplay.cs index f1a2a87..982fb27 100644 --- a/App/UI/TextDisplay.cs +++ b/App/UI/TextDisplay.cs @@ -12,6 +12,10 @@ namespace Game.UI { 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; @@ -72,6 +76,11 @@ namespace Game.UI { 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); + } + void DrawTitle(int y, int bgColor) { string text1 = "КТО ХОЧЕТ СТАТЬ"; string text2 = "СЕКУНДОМЕРОМ"; @@ -104,6 +113,75 @@ namespace Game.UI { 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); @@ -152,6 +230,51 @@ namespace Game.UI { //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 lines = new List(); + 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