1 /// contains the terminal object class 2 module ysge.ui.terminal; 3 4 import std.stdio; 5 import std.string; 6 import std.process; 7 import std.algorithm; 8 import core.stdc.stdlib; 9 import bindbc.sdl; 10 import ysge.project; 11 12 /// colours for the default pallette 13 enum Colour { 14 Black = 0, 15 Red = 1, 16 Green = 2, 17 Yellow = 3, 18 Blue = 4, 19 Purple = 5, 20 Cyan = 6, 21 White = 7, 22 Grey = 8, 23 BrightRed = 9, 24 BrightGreen = 10, 25 BrightYellow = 11, 26 BrightBlue = 12, 27 BrightPurple = 13, 28 BrightCyan = 14, 29 BrightWhite = 15 30 } 31 32 /// attributes for each character in the text buffer 33 struct Attributes { 34 ubyte fg = 7; /// foreground colour 35 ubyte bg = 0; /// background colour 36 } 37 38 /// cell structure (1 text buffer character) 39 struct Cell { 40 char ch = ' '; 41 Attributes attr; 42 43 /// creates a cell from a character with default attributes 44 static Cell FromChar(char ch) { 45 Cell ret; 46 Attributes attr; 47 48 ret.attr = attr; 49 ret.ch = ch; 50 51 return ret; 52 } 53 } 54 55 /// text screen object class 56 class Terminal : UIElement { 57 Vec2!int pos; 58 Cell[][] cells; 59 Vec2!int cellSize; 60 SDL_Color[] palette; 61 SDL_Texture*[256] characters; 62 63 /// initialises text screen with the default palette 64 /// and also creates the characters 65 this(Project parent) { 66 // default pallette 67 palette = [ 68 // https://gogh-co.github.io/Gogh/ 69 // Pro colour scheme 70 71 /* 0 */ HexToColour(0x000000), 72 /* 1 */ HexToColour(0x990000), 73 /* 2 */ HexToColour(0x00A600), 74 /* 3 */ HexToColour(0x999900), 75 /* 4 */ HexToColour(0x2009DB), 76 /* 5 */ HexToColour(0xB200B2), 77 /* 6 */ HexToColour(0x00A6B2), 78 /* 7 */ HexToColour(0xBFBFBF), 79 /* 8 */ HexToColour(0x666666), 80 /* 9 */ HexToColour(0xE50000), 81 /* A */ HexToColour(0x00D900), 82 /* B */ HexToColour(0xE5E500), 83 /* C */ HexToColour(0x0000FF), 84 /* D */ HexToColour(0xE500E5), 85 /* E */ HexToColour(0x00E5E5), 86 /* F */ HexToColour(0xE5E5E5) 87 ]; 88 89 foreach (i, ref texture ; characters) { 90 size_t[] dontRender = [0, 173]; 91 if (dontRender.canFind(i)) { 92 texture = null; 93 continue; 94 } 95 96 auto colour = SDL_Colour(255, 255, 255, 255); 97 98 string str; 99 str ~= cast(char) i; 100 101 SDL_Surface* textSurface = TTF_RenderText_Solid( 102 parent.font, toStringz(str), colour 103 ); 104 105 if (textSurface is null) { 106 stderr.writefln( 107 "Failed to render text: %s", fromStringz(TTF_GetError()) 108 ); 109 exit(1); 110 } 111 112 texture = SDL_CreateTextureFromSurface(parent.renderer, textSurface); 113 if (texture is null) { 114 stderr.writefln( 115 "Failed to create texture: %s", fromStringz(TTF_GetError()) 116 ); 117 exit(1); 118 } 119 } 120 } 121 122 /// copies this UI element to a new class instance 123 Terminal CreateCopy(Project parent) { 124 auto ret = new Terminal(parent); 125 126 ret.pos = pos; 127 ret.cells = cells; 128 ret.cellSize = cellSize; 129 ret.palette = palette; 130 ret.characters = characters; 131 132 return ret; 133 } 134 135 /// returns the size of the text buffer 136 Vec2!size_t GetSize() { 137 return Vec2!size_t(cells[0].length, cells.length); 138 } 139 140 /// sets the size of the text buffer 141 void SetSize(Vec2!size_t size) { 142 auto newCells = new Cell[][](size.y, size.x); 143 144 foreach (i, ref line ; cells) { 145 foreach (j, ref cell ; line) { 146 newCells[i][j] = cell; 147 } 148 } 149 150 cells = newCells; 151 } 152 153 /// sets a character in the text buffer 154 void SetCharacter( 155 Vec2!size_t pos, char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black 156 ) { 157 try { 158 cells[pos.y][pos.x] = Cell(ch, Attributes(fg, bg)); 159 } 160 catch (Throwable) { 161 return; 162 } 163 } 164 165 /// adds characters starting from pos in the text buffer 166 void WriteString( 167 Vec2!size_t pos, string str, ubyte fg = Colour.White, ubyte bg = Colour.Black 168 ) { 169 foreach (i, ref ch ; str) { 170 SetCharacter(Vec2!size_t(pos.x + i, pos.y), ch, fg, bg); 171 } 172 } 173 174 /// writes a string centered horizontally 175 void WriteStringCentered( 176 size_t yPos, string str, ubyte fg = Colour.White, ubyte bg = Colour.Black 177 ) { 178 Vec2!size_t pos; 179 pos.y = yPos; 180 pos.x = (GetSize().x / 2) - (str.length / 2); 181 182 WriteString(pos, str, fg, bg); 183 } 184 185 /// writes multiple lines in the text buffer 186 void WriteStringLines( 187 Vec2!size_t pos, string[] strings, 188 ubyte fg = Colour.White, ubyte bg = Colour.Black 189 ) { 190 Vec2!size_t ipos = pos; 191 192 for (size_t i = 0; i < strings.length; ++ i, ++ ipos.y) { 193 WriteString(ipos, strings[i], fg, bg); 194 } 195 } 196 197 /// writes multiple horizontally centered lines 198 void WriteStringLinesCentered( 199 size_t yPos, string[] strings, ubyte fg = Colour.White, 200 ubyte bg = Colour.Black, 201 ) { 202 size_t iy = yPos; 203 204 for (size_t i = 0; i < strings.length; ++ i, ++ iy) { 205 WriteStringCentered(iy, strings[i], fg, bg); 206 } 207 } 208 209 /// draws a horizontal line in the text buffer 210 void HorizontalLine( 211 Vec2!size_t start, size_t length, char ch, 212 ubyte fg = Colour.White, ubyte bg = Colour.Black 213 ) { 214 for (size_t x = start.x; x < start.x + length; ++ x) { 215 SetCharacter(Vec2!size_t(x, start.y), ch, fg, bg); 216 } 217 } 218 219 /// draws a vertical line in the text buffer 220 void VerticalLine( 221 Vec2!size_t start, size_t length, char ch, 222 ubyte fg = Colour.White, ubyte bg = Colour.Black 223 ) { 224 for (size_t y = start.y; y < start.y + length; ++ y) { 225 SetCharacter(Vec2!size_t(start.x, y), ch, fg, bg); 226 } 227 } 228 229 /// sets a cell in the text buffer to the given cell 230 void SetCell(Vec2!size_t pos, Cell cell) { 231 try { 232 cells[pos.y][pos.x] = cell; 233 } 234 catch (Throwable) { 235 236 } 237 } 238 239 /// fills the text buffer with the given character 240 void Clear(char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black) { 241 foreach (y, ref line ; cells) { 242 foreach (x, ref cell ; line) { 243 cell.ch = ch; 244 cell.attr.fg = fg; 245 cell.attr.bg = bg; 246 } 247 } 248 } 249 250 /// fills a rectangle 251 void FillRect( 252 SDL_Rect rect, char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black 253 ) { 254 for (size_t i = rect.y; i < rect.y + rect.h; ++ i) { 255 for (size_t j = rect.x; j < rect.x + rect.w; ++ j) { 256 cells[i][j] = Cell(ch, Attributes(fg, bg)); 257 } 258 } 259 } 260 261 /// draws the outline of a rectangle 262 void DrawBox( 263 SDL_Rect rect, ubyte fg = Colour.White, ubyte bg = Colour.Black 264 ) { 265 for (size_t i = rect.y + 1; i < rect.y + rect.h - 1; ++ i) { 266 Cell cell = Cell(0xB3, Attributes(fg, bg)); 267 268 cells[i][rect.x] = cell; 269 cells[i][rect.x + rect.w - 1] = cell; 270 } 271 272 for (size_t i = rect.x + 1; i < rect.x + rect.w - 1; ++ i) { 273 Cell cell = Cell(0xC4, Attributes(fg, bg)); 274 275 cells[rect.y][i] = cell; 276 cells[rect.y + rect.h - 1][i] = cell; 277 } 278 279 cells[rect.y][rect.x] = Cell(0xDA, Attributes(fg, bg)); 280 cells[rect.y + rect.h - 1][rect.x] = Cell(0xC0, Attributes(fg, bg)); 281 cells[rect.y][rect.x + rect.w - 1] = Cell(0xBF, Attributes(fg, bg)); 282 283 cells[rect.y + rect.h - 1][rect.x + rect.w - 1] = Cell( 284 0xD9, Attributes(fg, bg) 285 ); 286 } 287 288 override bool HandleEvent(Project parent, SDL_Event e) { 289 return false; 290 } 291 292 override void Render(Project parent) { 293 foreach (i, ref line ; cells) { 294 foreach (j, ch ; line) { 295 auto rect = SDL_Rect( 296 (cast(int) j * cellSize.x) + pos.x, 297 (cast(int) i * cellSize.y) + pos.y, 298 cellSize.x, 299 cellSize.y 300 ); 301 302 SDL_Color fg = palette[ch.attr.fg]; 303 SDL_Color bg = palette[ch.attr.bg]; 304 305 SDL_SetRenderDrawColor(parent.renderer, bg.r, bg.g, bg.b, 255); 306 SDL_RenderFillRect(parent.renderer, &rect); 307 308 if (ch.ch != ' ') { 309 /*text.DrawCharacter( 310 video.renderer, ch.ch, Vec2!int(rect.x, rect.y), fg 311 );*/ 312 313 if (characters[ch.ch] is null) { 314 continue; 315 } 316 317 SDL_SetTextureColorMod(characters[ch.ch], fg.r, fg.g, fg.b); 318 319 SDL_RenderCopy( 320 parent.renderer, characters[ch.ch], null, &rect 321 ); 322 } 323 } 324 } 325 } 326 } 327