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 }