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 }