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 }