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