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