1 module aurorafw.cli.terminal.window;
2 
3 import std.conv : to;
4 import std..string : fromStringz, toStringz;
5 
6 import riverd.ncurses;
7 
8 version (unittest) import aurorafw.unit.assertion;
9 
10 struct Window
11 {
12 	this(
13 			int _xOrigin,
14 			int _yOrigin,
15 			int _width,
16 			int _height,
17 			Position p = Position.ORIGIN)
18 	{
19 		if (p == Position.CENTER)
20 			window = newwin(_height, _width, _yOrigin - _height / 2, _xOrigin - _width / 2);
21 		else
22 			window = newwin(_height, _width, _yOrigin, _xOrigin);
23 
24 		setWindowValues();
25 	}
26 
27 	~this()
28 	{
29 		delwin(window);
30 	}
31 
32 	bool opEquals(const Window o) const
33 	{
34 		return (_xOrigin == o._xOrigin &&
35 				_yOrigin == o._yOrigin &&
36 				_width == o._width &&
37 				_height == o._height);
38 	}
39 
40 	bool opEquals(const WINDOW* o) const
41 	{
42 		return (_xOrigin == getbegx(o) &&
43 				_yOrigin == getbegy(o) &&
44 				_width == getmaxx(o) &&
45 				_height == getmaxy(o));
46 	}
47 
48 	void opIndexAssign(in char value, in int x, in int y)
49 	in
50 	{
51 		assert(x >= 0 &&
52 				y >= 0 &&
53 				x <= _width &&
54 				y <= _height);
55 	}
56 	do
57 	{
58 		mvwaddch(window, y, x, value);
59 	}
60 
61 	void opIndexAssign(in string value, in int x, in int y)
62 	in
63 	{
64 		assert(x >= 0 &&
65 				y >= 0 &&
66 				x <= _width &&
67 				y <= _height);
68 	}
69 	do
70 	{
71 		mvwaddstr(window, y, x, toStringz(value));
72 	}
73 
74 	void opIndexAssign(in char value, int[2] length, int y)
75 	in
76 	{
77 		assert(length[0] >= 0 &&
78 				length[1] <= _width &&
79 				y >= 0 &&
80 				y <= _height);
81 	}
82 	do
83 	{
84 		for (int x = length[0]; x <= length[1]; x++)
85 		{
86 			this.opIndexAssign(value, x, y);
87 		}
88 	}
89 
90 	void opIndexAssign(in char value, int x, int[2] length)
91 	in
92 	{
93 		assert(x >= 0 &&
94 				x <= _width &&
95 				length[0] >= 0 &&
96 				length[1] <= _height);
97 	}
98 	do
99 	{
100 		for (int y = length[0]; y <= length[1]; y++)
101 		{
102 			this.opIndexAssign(value, x, y);
103 		}
104 	}
105 
106 	void opIndexAssign(in char value, int[2] _width, int[2] _height)
107 	in
108 	{
109 		assert(_width[0] >= 0 &&
110 				_width[1] <= this._width &&
111 				_height[0] >= 0 &&
112 				_height[1] <= this._height);
113 	}
114 	do
115 	{
116 		for (int y = _height[0]; y <= _height[1]; y++)
117 		{
118 			this.opIndexAssign(value, _width, y);
119 		}
120 	}
121 
122 	char opIndex(in int x, in int y)
123 	in
124 	{
125 		assert(x >= _xOrigin &&
126 				y >= _yOrigin &&
127 				x <= _width &&
128 				y <= _height);
129 	}
130 	do
131 	{
132 		return to!char(mvwinch(window, y, x) & A_CHARTEXT);
133 	}
134 
135 	int[2] opSlice(size_t dim)(int start, int end) if (dim >= 0 && dim < 2)
136 	in
137 	{
138 		assert(start >= 0 && end <= this.opDollar!dim);
139 	}
140 	do
141 	{
142 		return [start, end];
143 	}
144 
145 	string opIndex(int[2] length, int y)
146 	{
147 		const int n = length[1] - length[0];
148 		char[] str = new char[n + 1];
149 		mvwinnstr(window, y, length[0], str.ptr, cast(int)(str.length));
150 		return str[0 .. $ - 1].dup;
151 	}
152 
153 	string[] opIndex(int x, int[2] length)
154 	{
155 		const int n = length[1] - length[0];
156 		string[] str;
157 
158 		for (int y = length[0]; y < length[1]; y++)
159 		{
160 			str ~= to!(string)(to!char(mvwinch(window, y, x) & A_CHARTEXT));
161 		}
162 		return str;
163 	}
164 
165 	string[] opIndex(int[2] _width, int[2] _height)
166 	{
167 		const int yn = _height[1] - _height[0];
168 		string[] matrix;
169 
170 		for (int y = _height[0]; y < _height[1]; y++)
171 		{
172 			matrix ~= this.opIndex(_width, y);
173 		}
174 
175 		return matrix;
176 	}
177 
178 	int opDollar(size_t dim : 0)() @property
179 	{
180 		return _width;
181 	}
182 
183 	int opDollar(size_t dim : 1)() @property
184 	{
185 		return _height;
186 	}
187 
188 	public enum Position
189 	{
190 		ORIGIN,
191 		CENTER
192 	}
193 
194 	private void setWindowValues()
195 	{
196 		wrefresh(window);
197 		_xOrigin = getbegx(window);
198 		_yOrigin = getbegy(window);
199 		_width = getmaxx(window);
200 		_height = getmaxy(window);
201 	}
202 
203 	public char readInputCh()
204 	{
205 		return to!char(wgetch(window) & A_CHARTEXT);
206 	}
207 
208 	public char readInputCh(int x, int y)
209 	{
210 		return to!char(mvwgetch(window, y, x) & A_CHARTEXT);
211 	}
212 
213 	public string readInputStr()
214 	{
215 		char[] str;
216 		do
217 		{
218 			str ~= readInputCh();
219 		}
220 		while (str[$ - 1] != '\n' && str[$ - 1] != '\r');
221 
222 		return fromStringz(str.ptr).dup;
223 	}
224 
225 	public string readInputStr(int n)
226 	{
227 		char[] str = new char[n];
228 		wgetnstr(window, str.ptr, n);
229 		return str.dup;
230 	}
231 
232 	public void moveWindow(int x, int y)
233 	{
234 		mvwin(window, y, x);
235 		setWindowValues();
236 	}
237 
238 	public void moveCursor(int x, int y)
239 	{
240 		wmove(window, y, x);
241 		setWindowValues();
242 	}
243 
244 	public void resizeWindow(int _width, int _height)
245 	{
246 		wresize(window, _height, _width);
247 		setWindowValues();
248 	}
249 
250 	public void addCh(in char c)
251 	{
252 		waddch(window, c);
253 	}
254 
255 	public void addStr(in string str)
256 	{
257 		waddstr(window, toStringz(str));
258 	}
259 
260 	public void writeFormated(A...)(string fmt, A args)
261 	{
262 		wprintw(window, toStringz(fmt), args);
263 	}
264 
265 	public void writeFormated(A...)(int[2] coords, string fmt, A args)
266 	{
267 		mvwprintw(window, coords[1], coords[0], toStringz(fmt), args);
268 	}
269 
270 	public void fill(in char c)
271 	{
272 		this.opIndexAssign(c, [0, _width], [0, _height]);
273 	}
274 
275 	public void clear()
276 	{
277 		wclear(window);
278 	}
279 
280 	public int cursorPositionX() const @property
281 	{
282 		return getcurx(window);
283 	}
284 
285 	public int cursorPositionY() const @property
286 	{
287 		return getcury(window);
288 	}
289 
290 	public int[2] cursorPosition() const @property
291 	{
292 		return [cursorPositionX, cursorPositionY];
293 	}
294 
295 	public void xOrigin(int value) @property
296 	{
297 		_xOrigin = value;
298 		wmove(window, _yOrigin, _xOrigin);
299 	}
300 
301 	public void yOrigin(int value) @property
302 	{
303 		_yOrigin = value;
304 		wmove(window, _yOrigin, _xOrigin);
305 	}
306 
307 	public void width(int value) @property
308 	{
309 		resizeWindow(value, _height);
310 	}
311 
312 	public void height(int value) @property
313 	{
314 		resizeWindow(_width, value);
315 	}
316 
317 	public int xCenter() const @property
318 	{
319 		return (_xOrigin + _width) / 2;
320 	}
321 
322 	public int yCenter() const @property
323 	{
324 		return (_yOrigin + _height) / 2;
325 	}
326 
327 	public int[2] xyCenter() const @property
328 	{
329 		return [xCenter, yCenter];
330 	}
331 
332 	public int xEnd() const @property
333 	{
334 		return _xOrigin + _width;
335 	}
336 
337 	public int yEnd() const @property
338 	{
339 		return _yOrigin + _height;
340 	}
341 
342 	public int[2] xyEnd() const @property
343 	{
344 		return [xEnd, yEnd];
345 	}
346 
347 	public WINDOW* window;
348 	private int _xOrigin, _yOrigin;
349 	private int _width, _height;
350 }
351 
352 @("Window: Comparing windows")
353 unittest
354 {
355 	initscr();
356 
357 	Window win1 = Window(5, 0, 10, 3);
358 	Window win2 = Window(5, 0, 10, 3);
359 	WINDOW* win3 = newwin(3, 10, 0, 5);
360 
361 	assertTrue(win1 == win2);
362 	assertTrue(win2 == win3);
363 
364 	endwin();
365 }
366 
367 @("Window: Center position")
368 unittest
369 {
370 	initscr();
371 
372 	Window win1 = Window(5, 5, 5, 5, Window.Position.CENTER);
373 	Window win2 = Window(3, 3, 5, 5);
374 
375 	assertTrue(win1 == win2);
376 
377 	assertTrue(win1.xCenter == win2.xCenter);
378 	assertTrue(win1.yCenter == win2.yCenter);
379 	assertTrue(win1.xyCenter == win2.xyCenter);
380 
381 	endwin();
382 }
383 
384 @("Window: Writing and reading")
385 unittest
386 {
387 	initscr();
388 
389 	Window win = Window(1, 1, 13, 11);
390 	win[0, 0] = "Hello World";
391 	win[1, 2] = 'c';
392 
393 	assertTrue(win[1, 2] == 'c');
394 
395 	assertTrue(win[0 .. 11, 0] == "Hello World");
396 	assertTrue(win[1, 0 .. 3] == ["e", " ", "c"]);
397 
398 	assertTrue(win[0 .. 13, 0] == win[0 .. $, 0]);
399 
400 	assertTrue(win[0 .. 11, 0 .. 3] == ["Hello World",
401 			"           ",
402 			" c         "]);
403 
404 	endwin();
405 }
406 
407 @("Window: Writing formated")
408 unittest
409 {
410 	initscr();
411 
412 	Window win = Window(0, 0, 50, 10);
413 	win.writeFormated([0, 0], "Hello World n. %d!", 3432);
414 
415 	assertTrue(win[0 .. "Hello World n. 3432!".length, 0] == "Hello World n. 3432!");
416 
417 	endwin();
418 }
419 
420 @("Window: Writing a char n times")
421 unittest
422 {
423 	initscr();
424 
425 	Window win = Window(0, 0, 10, 10);
426 
427 	win[0 .. 2, 0] = 'H';
428 	win[0, 1 .. 3] = 'T';
429 	win[5 .. $, 5 .. $] = 'O';
430 
431 	assertTrue(win[0 .. 3, 0] == "HHH");
432 	assertTrue(win[0, 1 .. 4] == ["T", "T", "T"]);
433 	assertTrue(win[5 .. $, 5 .. $] == ["OOOOO",
434 			"OOOOO",
435 			"OOOOO",
436 			"OOOOO",
437 			"OOOOO"]);
438 
439 	endwin();
440 }
441 
442 @("Window: Resizing and moving")
443 unittest
444 {
445 	initscr();
446 
447 	Window win = Window(0, 0, 5, 5);
448 
449 	win.resizeWindow(10, 10);
450 	assertTrue(win == Window(0, 0, 10, 10));
451 
452 	win.moveWindow(2, 3);
453 	assertTrue(win == Window(2, 3, 10, 10));
454 
455 	endwin();
456 }
457 
458 @("Window: Cursor movement")
459 unittest
460 {
461 	initscr();
462 
463 	Window win = Window(0, 0, 10, 10);
464 	win.moveCursor(5, 7);
465 	assertTrue(win.cursorPosition == [5, 7]);
466 
467 	endwin();
468 }
469 
470 @("Window: Adding a string or char")
471 unittest
472 {
473 	initscr();
474 
475 	Window win = Window(0, 0, 10, 10);
476 	win.addCh('c');
477 	assertTrue(win[0, 0] == 'c');
478 
479 	win.addStr("string");
480 	assertTrue(win[0 .. 6, 0] == "string");
481 
482 	endwin();
483 }
484 
485 @("Window: Fill and clear")
486 unittest
487 {
488 	initscr();
489 
490 	Window win = Window(0, 0, 3, 3);
491 
492 	assertTrue(win[0 .. $, 0 .. $] == ["   ",
493 			"   ",
494 			"   "]);
495 
496 	win.fill('F');
497 	assertTrue(win[0 .. $, 0 .. $] == ["FFF",
498 			"FFF",
499 			"FFF"]);
500 
501 	win.clear();
502 	assertTrue(win[0 .. $, 0 .. $] == ["   ",
503 			"   ",
504 			"   "]);
505 
506 	endwin();
507 }
508 
509 @("Window: Change a single value")
510 unittest
511 {
512 	initscr();
513 
514 	Window win = Window(0, 0, 10, 10);
515 	win.xOrigin = 5;
516 	win.yOrigin = 8;
517 
518 	assertTrue(win == Window(5, 8, 10, 10));
519 
520 	win.width = 25;
521 	win.height = 15;
522 
523 	assertTrue(win == Window(0, 0, 25, 15));
524 
525 	assertTrue(win.xEnd == 25);
526 	assertTrue(win.yEnd == 15);
527 	assertTrue(win.xyEnd == [25, 15]);
528 
529 	endwin();
530 }