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