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 }