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 TextScreen : UIElement {
57 	Cell[][]          cells;
58 	Vec2!int          cellSize;
59 	SDL_Color[]       palette;
60 	SDL_Texture*[256] characters;
61 
62 	/// initialises text screen with the default palette 
63 	/// and also creates the characters
64 	this(Project parent) {
65 		// default pallette
66 		palette = [
67 			// https://gogh-co.github.io/Gogh/
68 			// Pro colour scheme
69 			
70 			/* 0 */ HexToColour(0x000000),
71 			/* 1 */ HexToColour(0x990000),
72 			/* 2 */ HexToColour(0x00A600),
73 			/* 3 */ HexToColour(0x999900),
74 			/* 4 */ HexToColour(0x2009DB),
75 			/* 5 */ HexToColour(0xB200B2),
76 			/* 6 */ HexToColour(0x00A6B2),
77 			/* 7 */ HexToColour(0xBFBFBF),
78 			/* 8 */ HexToColour(0x666666),
79 			/* 9 */ HexToColour(0xE50000),
80 			/* A */ HexToColour(0x00D900),
81 			/* B */ HexToColour(0xE5E500),
82 			/* C */ HexToColour(0x0000FF),
83 			/* D */ HexToColour(0xE500E5),
84 			/* E */ HexToColour(0x00E5E5),
85 			/* F */ HexToColour(0xE5E5E5)
86 		];
87 
88 		foreach (i, ref texture ; characters) {
89 			size_t[] dontRender = [0, 173];
90 			if (dontRender.canFind(i)) {
91 				texture = null;
92 				continue;
93 			}
94 		
95 			auto colour = SDL_Colour(255, 255, 255, 255);
96 
97 			string str;
98 			str ~= cast(char) i;
99 		
100 			SDL_Surface* textSurface = TTF_RenderText_Solid(
101 				parent.font, toStringz(str), colour
102 			);
103 			
104 			if (textSurface is null) {
105 				stderr.writefln(
106 					"Failed to render text: %s", fromStringz(TTF_GetError())
107 				);
108 				exit(1);
109 			}
110 			
111 			texture = SDL_CreateTextureFromSurface(parent.renderer, textSurface);
112 			if (texture is null) {
113 				stderr.writefln(
114 					"Failed to create texture: %s", fromStringz(TTF_GetError())
115 				);
116 				exit(1);
117 			}
118 		}
119 	}
120 
121 	/// returns the size of the text buffer
122 	Vec2!size_t GetSize() {
123 		return Vec2!size_t(cells[0].length, cells.length);
124 	}
125 
126 	/// sets the size of the text buffer
127 	void SetSize(Vec2!size_t size) {
128 		auto newCells = new Cell[][](size.y, size.x);
129 
130 		foreach (i, ref line ; cells) {
131 			foreach (j, ref cell ; line) {
132 				newCells[i][j] = cell;
133 			}
134 		}
135 
136 		cells = newCells;
137 	}
138 
139 	/// sets a character in the text buffer
140 	void SetCharacter(
141 		Vec2!size_t pos, char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black
142 	) {
143 		try {	
144 			cells[pos.y][pos.x] = Cell(ch, Attributes(fg, bg));
145 		}
146 		catch (Throwable) {
147 			return;
148 		}
149 	}
150 
151 	/// adds characters starting from pos in the text buffer
152 	void WriteString(
153 		Vec2!size_t pos, string str, ubyte fg = Colour.White, ubyte bg = Colour.Black
154 	) {
155 		foreach (i, ref ch ; str) {
156 			SetCharacter(Vec2!size_t(pos.x + i, pos.y), ch, fg, bg);
157 		}
158 	}
159 
160 	/// writes a string centered horizontally
161 	void WriteStringCentered(
162 		size_t yPos, string str, ubyte fg = Colour.White, ubyte bg = Colour.Black
163 	) {
164 		Vec2!size_t pos;
165 		pos.y = yPos;
166 		pos.x = (GetSize().x / 2) - (str.length / 2);
167 
168 		WriteString(pos, str, fg, bg);
169 	}
170 
171 	/// writes multiple lines in the text buffer
172 	void WriteStringLines(
173 		Vec2!size_t pos, string[] strings,
174 		ubyte fg = Colour.White, ubyte bg = Colour.Black
175 	) {
176 		Vec2!size_t ipos = pos;
177 
178 		for (size_t i = 0; i < strings.length; ++ i, ++ ipos.y) {
179 			WriteString(ipos, strings[i], fg, bg);
180 		}
181 	}
182 
183 	/// writes multiple horizontally centered lines
184 	void WriteStringLinesCentered(
185 		size_t yPos, string[] strings, ubyte fg = Colour.White,
186 		ubyte bg = Colour.Black,
187 	) {
188 		size_t iy = yPos;
189 
190 		for (size_t i = 0; i < strings.length; ++ i, ++ iy) {
191 			WriteStringCentered(iy, strings[i], fg, bg);
192 		}
193 	}
194 
195 	/// draws a horizontal line in the text buffer
196 	void HorizontalLine(
197 		Vec2!size_t start, size_t length, char ch,
198 		ubyte fg = Colour.White, ubyte bg = Colour.Black
199 	) {
200 		for (size_t x = start.x; x < start.x + length; ++ x) {
201 			SetCharacter(Vec2!size_t(x, start.y), ch, fg, bg);
202 		}
203 	}
204 
205 	/// draws a vertical line in the text buffer
206 	void VerticalLine(
207 		Vec2!size_t start, size_t length, char ch,
208 		ubyte fg = Colour.White, ubyte bg = Colour.Black
209 	) {
210 		for (size_t y = start.y; y < start.y + length; ++ y) {
211 			SetCharacter(Vec2!size_t(start.x, y), ch, fg, bg);
212 		}
213 	}
214 
215 	/// sets a cell in the text buffer to the given cell
216 	void SetCell(Vec2!size_t pos, Cell cell) {
217 		try {
218 			cells[pos.y][pos.x] = cell;
219 		}
220 		catch (Throwable) {
221 			
222 		}
223 	}
224 
225 	/// fills the text buffer with the given character
226 	void Clear(char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black) {
227 		foreach (y, ref line ; cells) {
228 			foreach (x, ref cell ; line) {
229 				cell.ch      = ch;
230 				cell.attr.fg = fg;
231 				cell.attr.bg = bg;
232 			}
233 		}
234 	}
235 
236 	/// fills a rectangle
237 	void FillRect(
238 		SDL_Rect rect, char ch, ubyte fg = Colour.White, ubyte bg = Colour.Black
239 	) {
240 		for (size_t i = rect.y; i < rect.y + rect.h; ++ i) {
241 			for (size_t j = rect.x; j < rect.x + rect.w; ++ j) {
242 				cells[i][j] = Cell(ch, Attributes(fg, bg));
243 			}
244 		}
245 	}
246 
247 	/// draws the outline of a rectangle
248 	void DrawBox(
249 		SDL_Rect rect, ubyte fg = Colour.White, ubyte bg = Colour.Black
250 	) {
251 		for (size_t i = rect.y + 1; i < rect.y + rect.h - 1; ++ i) {
252 			Cell cell = Cell(0xB3, Attributes(fg, bg));
253 			
254 			cells[i][rect.x]              = cell;
255 			cells[i][rect.x + rect.w - 1] = cell;
256 		}
257 
258 		for (size_t i = rect.x + 1; i < rect.x + rect.w - 1; ++ i) {
259 			Cell cell = Cell(0xC4, Attributes(fg, bg));
260 
261 			cells[rect.y][i]              = cell;
262 			cells[rect.y + rect.h - 1][i] = cell;
263 		}
264 
265 		cells[rect.y][rect.x]              = Cell(0xDA, Attributes(fg, bg));
266 		cells[rect.y + rect.h - 1][rect.x] = Cell(0xC0, Attributes(fg, bg));
267 		cells[rect.y][rect.x + rect.w - 1] = Cell(0xBF, Attributes(fg, bg));
268 		
269 		cells[rect.y + rect.h - 1][rect.x + rect.w - 1] = Cell(
270 			0xD9, Attributes(fg, bg)
271 		);
272 	}
273 
274 	override bool HandleEvent(Project parent, SDL_Event e) {
275 		return false;
276 	}
277 
278 	override void Render(Project parent) {
279 		foreach (i, ref line ; cells) {
280 			foreach (j, ch ; line) {
281 				auto rect = SDL_Rect(
282 					cast(int) j * cellSize.x,
283 					cast(int) i * cellSize.y,
284 					cellSize.x,
285 					cellSize.y
286 				);
287 
288 				SDL_Color fg = palette[ch.attr.fg];
289 				SDL_Color bg = palette[ch.attr.bg];
290 
291 				SDL_SetRenderDrawColor(parent.renderer, bg.r, bg.g, bg.b, 255);
292 				SDL_RenderFillRect(parent.renderer, &rect);
293 
294 				if (ch.ch != ' ') {
295 					/*text.DrawCharacter(
296 						video.renderer, ch.ch, Vec2!int(rect.x, rect.y), fg
297 					);*/
298 
299 					if (characters[ch.ch] is null) {
300 						continue;
301 					}
302 
303 					SDL_RenderCopy(
304 						parent.renderer, characters[ch.ch], null, &rect
305 					);
306 				}
307 			}
308 		}
309 	}
310 }
311