1 module dlangui.platforms.ansi_console.consoleapp; 2 3 public import dlangui.core.config; 4 static if (BACKEND_CONSOLE): 5 6 import dlangui.core.logger; 7 import dlangui.platforms.common.platform; 8 import dlangui.graphics.drawbuf; 9 import dlangui.graphics.fonts; 10 import dlangui.widgets.styles; 11 import dlangui.widgets.widget; 12 import dlangui.platforms.ansi_console.consolefont; 13 private import dlangui.platforms.ansi_console.dconsole; 14 15 class ConsoleWindow : Window { 16 ConsolePlatform _platform; 17 ConsoleWindow _parent; 18 this(ConsolePlatform platform, dstring caption, Window parent, uint flags) { 19 super(); 20 _platform = platform; 21 _parent = cast(ConsoleWindow)parent; 22 _dx = _platform.console.width; 23 _dy = _platform.console.height; 24 _currentContentWidth = _dx; 25 _currentContentHeight = _dy; 26 _windowRect = Rect(0, 0, _dx, _dy); 27 } 28 /// show window 29 override void show() { 30 if (!_mainWidget) { 31 Log.e("Window is shown without main widget"); 32 _mainWidget = new Widget(); 33 } 34 _visible = true; 35 handleWindowStateChange(WindowState.normal, Rect(0, 0, _platform.console.width, _platform.console.height)); 36 invalidate(); 37 } 38 private dstring _windowCaption; 39 /// returns window caption 40 override @property dstring windowCaption() const { 41 return _windowCaption; 42 } 43 /// sets window caption 44 override @property void windowCaption(dstring caption) { 45 _windowCaption = caption; 46 } 47 /// sets window icon 48 override @property void windowIcon(DrawBufRef icon) { 49 // ignore 50 } 51 /// request window redraw 52 override void invalidate() { 53 _platform.update(); 54 } 55 /// close window 56 override void close() { 57 Log.d("ConsoleWindow.close()"); 58 _platform.closeWindow(this); 59 } 60 61 override @property Window parentWindow() { 62 return _parent; 63 } 64 65 override protected void handleWindowActivityChange(bool isWindowActive) { 66 super.handleWindowActivityChange(isWindowActive); 67 } 68 69 override @property bool isActive() { 70 // todo 71 return true; 72 } 73 74 75 protected bool _visible; 76 /// returns true if window is shown 77 @property bool visible() { 78 return _visible; 79 } 80 } 81 82 class ConsolePlatform : Platform { 83 protected Console _console; 84 85 @property Console console() { return _console; } 86 87 protected ANSIConsoleDrawBuf _drawBuf; 88 this() { 89 _console = new Console(); 90 _console.batchMode = true; 91 _console.keyEvent = &onConsoleKey; 92 _console.mouseEvent = &onConsoleMouse; 93 _console.resizeEvent = &onConsoleResize; 94 _console.inputIdleEvent = &onInputIdle; 95 _console.init(); 96 _console.setCursorType(ConsoleCursorType.Invisible); 97 _uiDialogDisplayMode = DialogDisplayMode.allTypesOfDialogsInPopup; 98 _drawBuf = new ANSIConsoleDrawBuf(_console); 99 } 100 ~this() { 101 //Log.d("Destroying console"); 102 //destroy(_console); 103 Log.d("Destroying drawbuf"); 104 destroy(_drawBuf); 105 } 106 107 ConsoleWindow[] _windowList; 108 109 /** 110 * create window 111 * Args: 112 * windowCaption = window caption text 113 * parent = parent Window, or null if no parent 114 * flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen 115 * width = window width 116 * height = window height 117 * 118 * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget 119 */ 120 override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) { 121 ConsoleWindow res = new ConsoleWindow(this, windowCaption, parent, flags); 122 _windowList ~= res; 123 return res; 124 } 125 126 127 ConsoleWindow activeWindow() { 128 if (!_windowList.length) 129 return null; 130 return _windowList[$ - 1]; 131 } 132 133 @property DrawBuf drawBuf() { return _drawBuf; } 134 protected bool onConsoleKey(KeyEvent event) { 135 auto w = activeWindow; 136 if (!w) 137 return false; 138 if (w.dispatchKeyEvent(event)) { 139 _needRedraw = true; 140 return true; 141 } 142 return false; 143 } 144 145 protected bool onConsoleMouse(MouseEvent event) { 146 auto w = activeWindow; 147 if (!w) 148 return false; 149 if (w.dispatchMouseEvent(event)) { 150 _needRedraw = true; 151 return true; 152 } 153 return false; 154 } 155 156 protected bool onConsoleResize(int width, int height) { 157 drawBuf.resize(width, height); 158 foreach(w; _windowList) { 159 w.onResize(width, height); 160 } 161 _needRedraw = true; 162 return false; 163 } 164 165 protected bool _needRedraw = true; 166 void update() { 167 _needRedraw = true; 168 } 169 170 protected void redraw() { 171 if (!_needRedraw) 172 return; 173 foreach(w; _windowList) { 174 if (w.visible) { 175 _drawBuf.fillRect(Rect(0, 0, w.width, w.height), w.backgroundColor); 176 w.onDraw(_drawBuf); 177 auto caretRect = w.caretRect; 178 if ((w is activeWindow)) { 179 if (!caretRect.empty) { 180 _drawBuf.console.setCursor(caretRect.left, caretRect.top); 181 _drawBuf.console.setCursorType(w.caretReplace ? ConsoleCursorType.Replace : ConsoleCursorType.Insert); 182 } else { 183 _drawBuf.console.setCursorType(ConsoleCursorType.Invisible); 184 } 185 _drawBuf.console.setWindowCaption(w.windowCaption); 186 } 187 } 188 } 189 _needRedraw = false; 190 } 191 192 protected bool onInputIdle() { 193 checkClosedWindows(); 194 foreach(w; _windowList) { 195 w.pollTimers(); 196 w.handlePostedEvents(); 197 } 198 checkClosedWindows(); 199 redraw(); 200 _console.flush(); 201 return false; 202 } 203 204 protected Window[] _windowsToClose; 205 protected void handleCloseWindow(Window w) { 206 for (int i = 0; i < _windowList.length; i++) { 207 if (_windowList[i] is w) { 208 for (int j = i; j + 1 < _windowList.length; j++) 209 _windowList[j] = _windowList[j + 1]; 210 _windowList[$ - 1] = null; 211 _windowList.length--; 212 destroy(w); 213 return; 214 } 215 } 216 } 217 218 protected void checkClosedWindows() { 219 for (int i = 0; i < _windowsToClose.length; i++) { 220 handleCloseWindow(_windowsToClose[i]); 221 } 222 _windowsToClose.length = 0; 223 } 224 /** 225 * close window 226 * 227 * Closes window earlier created with createWindow() 228 */ 229 override void closeWindow(Window w) { 230 _windowsToClose ~= w; 231 } 232 /** 233 * Starts application message loop. 234 * 235 * When returned from this method, application is shutting down. 236 */ 237 override int enterMessageLoop() { 238 Log.i("Entered message loop"); 239 while (_console.pollInput()) { 240 if (_windowList.length == 0) { 241 Log.d("Window count is 0 - exiting message loop"); 242 243 break; 244 } 245 } 246 Log.i("Message loop finished - closing windows"); 247 _windowsToClose ~= _windowList; 248 checkClosedWindows(); 249 Log.i("Exiting from message loop"); 250 return 0; 251 } 252 private dstring _clipboardText; 253 254 /// check has clipboard text 255 override bool hasClipboardText(bool mouseBuffer = false) { 256 return (_clipboardText.length > 0); 257 } 258 259 /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 260 override dstring getClipboardText(bool mouseBuffer = false) { 261 return _clipboardText; 262 } 263 /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 264 override void setClipboardText(dstring text, bool mouseBuffer = false) { 265 _clipboardText = text; 266 } 267 268 /// calls request layout for all windows 269 override void requestLayout() { 270 // TODO 271 } 272 273 private void onCtrlC() { 274 Log.w("Ctrl+C pressed - stopping application"); 275 if (_console) { 276 _console.stop(); 277 } 278 } 279 } 280 281 /// drawing buffer - image container which allows to perform some drawing operations 282 class ANSIConsoleDrawBuf : ConsoleDrawBuf { 283 284 protected Console _console; 285 @property Console console() { return _console; } 286 287 this(Console console) { 288 _console = console; 289 resetClipping(); 290 } 291 292 ~this() { 293 Log.d("Calling console.uninit"); 294 _console.uninit(); 295 } 296 297 /// returns current width 298 override @property int width() { return _console.width; } 299 /// returns current height 300 override @property int height() { return _console.height; } 301 302 /// reserved for hardware-accelerated drawing - begins drawing batch 303 override void beforeDrawing() { 304 // TODO? 305 } 306 /// reserved for hardware-accelerated drawing - ends drawing batch 307 override void afterDrawing() { 308 // TODO? 309 } 310 /// returns buffer bits per pixel 311 override @property int bpp() { return 4; } 312 // returns pointer to ARGB scanline, null if y is out of range or buffer doesn't provide access to its memory 313 //uint * scanLine(int y) { return null; } 314 /// resize buffer 315 override void resize(int width, int height) { 316 // IGNORE 317 resetClipping(); 318 } 319 320 //======================================================== 321 // Drawing methods. 322 323 /// fill the whole buffer with solid color (no clipping applied) 324 override void fill(uint color) { 325 // TODO 326 fillRect(Rect(0, 0, width, height), color); 327 } 328 329 private struct RGB { 330 int r; 331 int g; 332 int b; 333 int match(int rr, int gg, int bb) immutable { 334 int dr = rr - r; 335 int dg = gg - g; 336 int db = bb - b; 337 if (dr < 0) dr = -dr; 338 if (dg < 0) dg = -dg; 339 if (db < 0) db = -db; 340 return dr + dg + db; 341 } 342 } 343 version(Windows) { 344 // windows color table 345 static immutable RGB[16] CONSOLE_COLORS_RGB = [ 346 RGB(0,0,0), 347 RGB(0,0,128), 348 RGB(0,128,0), 349 RGB(0,128,128), 350 RGB(128,0,0), 351 RGB(128,0,128), 352 RGB(128,128,0), 353 RGB(192,192,192), 354 RGB(0x7c,0x7c,0x7c), // ligth gray 355 RGB(0,0,255), 356 RGB(0,255,0), 357 RGB(0,255,255), 358 RGB(255,0,0), 359 RGB(255,0,255), 360 RGB(255,255,0), 361 RGB(255,255,255), 362 ]; 363 } else { 364 // linux color table 365 static immutable RGB[16] CONSOLE_COLORS_RGB = [ 366 RGB(0,0,0), 367 RGB(128,0,0), 368 RGB(0,128,0), 369 RGB(128,128,0), 370 RGB(0,0,128), 371 RGB(128,0,128), 372 RGB(0,128,128), 373 RGB(192,192,192), 374 RGB(0x7c,0x7c,0x7c), // ligth gray 375 RGB(255,0,0), 376 RGB(0,255,0), 377 RGB(255,255,0), 378 RGB(0,0,255), 379 RGB(255,0,255), 380 RGB(0,255,255), 381 RGB(255,255,255), 382 ]; 383 } 384 385 static ubyte toConsoleColor(uint color, bool forBackground = false) { 386 if (forBackground && ((color >> 24) & 0xFF) >= 0x80) 387 return CONSOLE_TRANSPARENT_BACKGROUND; 388 int r = (color >> 16) & 0xFF; 389 int g = (color >> 8) & 0xFF; 390 int b = (color >> 0) & 0xFF; 391 int bestMatch = CONSOLE_COLORS_RGB[0].match(r,g,b); 392 int bestMatchIndex = 0; 393 for (int i = 1; i < 16; i++) { 394 int m = CONSOLE_COLORS_RGB[i].match(r,g,b); 395 if (m < bestMatch) { 396 bestMatch = m; 397 bestMatchIndex = i; 398 } 399 } 400 return cast(ubyte)bestMatchIndex; 401 } 402 403 404 static immutable dstring SPACE_STRING = 405 " " 406 ~ " " 407 ~ " " 408 ~ " " 409 ~ " "; 410 411 /// fill rectangle with solid color (clipping is applied) 412 override void fillRect(Rect rc, uint color) { 413 uint alpha = color >> 24; 414 if (alpha >= 128) 415 return; // transparent 416 _console.backgroundColor = toConsoleColor(color); 417 if (applyClipping(rc)) { 418 int w = rc.width; 419 foreach(y; rc.top .. rc.bottom) { 420 _console.setCursor(rc.left, y); 421 _console.writeText(SPACE_STRING[0 .. w]); 422 } 423 } 424 } 425 426 override void fillGradientRect(Rect rc, uint color1, uint color2, uint color3, uint color4) { 427 // TODO 428 fillRect(rc, color1); 429 } 430 431 /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted 432 override void fillRectPattern(Rect rc, uint color, int pattern) { 433 // default implementation: does not support patterns 434 fillRect(rc, color); 435 } 436 437 /// draw pixel at (x, y) with specified color 438 override void drawPixel(int x, int y, uint color) { 439 // TODO 440 } 441 442 override void drawChar(int x, int y, dchar ch, uint color, uint bgcolor) { 443 if (x < _clipRect.left || x >= _clipRect.right || y < _clipRect.top || y >= _clipRect.bottom) 444 return; 445 ubyte tc = toConsoleColor(color, false); 446 ubyte bc = toConsoleColor(bgcolor, true); 447 dchar[1] text; 448 text[0] = ch; 449 _console.textColor = tc; 450 _console.backgroundColor = bc; 451 _console.setCursor(x, y); 452 _console.writeText(cast(dstring)text); 453 } 454 455 /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied) 456 override void drawGlyph(int x, int y, Glyph * glyph, uint color) { 457 // TODO 458 } 459 460 /// draw source buffer rectangle contents to destination buffer 461 override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) { 462 // not supported 463 } 464 465 /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling 466 override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) { 467 // not supported 468 } 469 470 override void clear() { 471 resetClipping(); 472 } 473 } 474 475 476 extern(C) void mySignalHandler(int value) { 477 Log.i("Signal handler - signal=", value); 478 ConsolePlatform platform = cast(ConsolePlatform)Platform.instance; 479 if (platform) { 480 platform.onCtrlC(); 481 } 482 } 483 484 //version (none): 485 // entry point for console app 486 extern(C) int DLANGUImain(string[] args) { 487 initLogs(); 488 SCREEN_DPI = 10; 489 Platform.setInstance(new ConsolePlatform()); 490 FontManager.instance = new ConsoleFontManager(); 491 initResourceManagers(); 492 493 version (Windows) { 494 import core.sys.windows.winuser; 495 DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime(); 496 } else { 497 // set Ctrl+C handler 498 import core.sys.posix.signal; 499 sigset(SIGINT, &mySignalHandler); 500 } 501 502 currentTheme = createDefaultTheme(); 503 Platform.instance.uiTheme = "theme_default"; 504 505 Log.i("Entering UIAppMain: ", args); 506 int result = -1; 507 version (unittest) { 508 result = 0; 509 } else { 510 try { 511 result = UIAppMain(args); 512 Log.i("UIAppMain returned ", result); 513 } catch (Exception e) { 514 Log.e("Abnormal UIAppMain termination"); 515 Log.e("UIAppMain exception: ", e); 516 } 517 } 518 519 Platform.setInstance(null); 520 521 releaseResourcesOnAppExit(); 522 523 Log.d("Exiting main"); 524 APP_IS_SHUTTING_DOWN = true; 525 526 return result; 527 }