1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains common Plaform definitions.
7 
8 Platform is abstraction layer for application.
9 
10 
11 Synopsis:
12 
13 ----
14 import dlangui.platforms.common.platform;
15 
16 ----
17 
18 Copyright: Vadim Lopatin, 2014
19 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
20 Authors:   $(WEB coolreader.org, Vadim Lopatin)
21 */
22 module dlangui.platforms.common.platform;
23 
24 public import dlangui.core.events;
25 import dlangui.widgets.widget;
26 import dlangui.widgets.popup;
27 import dlangui.graphics.drawbuf;
28 
29 private import dlangui.graphics.gldrawbuf;
30 
31 class Window {
32     protected int _dx;
33     protected int _dy;
34 	protected uint _backgroundColor;
35     protected Widget _mainWidget;
36 	@property uint backgroundColor() { return _backgroundColor; }
37 	@property void backgroundColor(uint color) { _backgroundColor = color; }
38     @property int width() { return _dx; }
39     @property int height() { return _dy; }
40     @property Widget mainWidget() { return _mainWidget; }
41     @property void mainWidget(Widget widget) { 
42         if (_mainWidget !is null)
43             _mainWidget.window = null;
44         _mainWidget = widget; 
45         if (_mainWidget !is null)
46             _mainWidget.window = this;
47     }
48     abstract void show();
49     abstract @property string windowCaption();
50     abstract @property void windowCaption(string caption);
51     void measure() {
52         if (_mainWidget !is null) {
53             _mainWidget.measure(_dx, _dy);
54         }
55         foreach(p; _popups)
56             p.measure(_dx, _dy);
57     }
58     void layout() {
59         Rect rc = Rect(0, 0, _dx, _dy);
60         if (_mainWidget !is null) {
61             _mainWidget.layout(rc);
62         }
63         foreach(p; _popups)
64             p.layout(rc);
65     }
66     void onResize(int width, int height) {
67         if (_dx == width && _dy == height)
68             return;
69         _dx = width;
70         _dy = height;
71         if (_mainWidget !is null) {
72             Log.d("onResize ", _dx, "x", _dy);
73             long measureStart = currentTimeMillis;
74             measure();
75             long measureEnd = currentTimeMillis;
76             Log.d("measure took ", measureEnd - measureStart, " ms");
77             layout();
78             long layoutEnd = currentTimeMillis;
79             Log.d("layout took ", layoutEnd - measureEnd, " ms");
80         }
81     }
82 
83     protected PopupWidget[] _popups;
84     /// show new popup
85     PopupWidget showPopup(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center) {
86         PopupWidget res = new PopupWidget(content, this);
87         res.anchor.widget = anchor !is null ? anchor : _mainWidget;
88         res.anchor.alignment = alignment;
89         _popups ~= res;
90         if (_mainWidget !is null)
91             _mainWidget.requestLayout();
92         return res;
93     }
94     /// remove popup
95     bool removePopup(PopupWidget popup) {
96         for (int i = 0; i < _popups.length; i++) {
97             PopupWidget p = _popups[i];
98             if (p is popup) {
99                 for (int j = i; j < _popups.length - 1; j++)
100                     _popups[j] = _popups[j + 1];
101                 _popups.length--;
102                 p.onClose();
103                 destroy(p);
104                 // force redraw
105                 _mainWidget.invalidate();
106                 return true;
107             }
108         }
109         return false;
110     }
111 
112     /// returns true if widget is child of either main widget or one of popups
113     bool isChild(Widget w) {
114         if (_mainWidget !is null && _mainWidget.isChild(w))
115             return true;
116         foreach(p; _popups)
117             if (p.isChild(w))
118                 return true;
119         return false;
120     }
121 
122     private long lastDrawTs;
123 
124 	this() {
125 		_backgroundColor = 0xFFFFFF;
126 	}
127 	~this() {
128 		if (_mainWidget !is null) {
129 			destroy(_mainWidget);
130 		    _mainWidget = null;
131 		}
132         foreach(p; _popups)
133             destroy(p);
134         _popups = null;
135 	}
136 
137     private void animate(Widget root, long interval) {
138         if (root is null)
139             return;
140         if (root.visibility != Visibility.Visible)
141             return;
142         for (int i = 0; i < root.childCount; i++)
143             animate(root.child(i), interval);
144         if (root.animating)
145             root.animate(interval);
146     }
147 
148     private void animate(long interval) {
149         animate(_mainWidget, interval);
150         foreach(p; _popups)
151             p.animate(interval);
152     }
153 
154     void onDraw(DrawBuf buf) {
155         bool needDraw = false;
156         bool needLayout = false;
157         bool animationActive = false;
158         checkUpdateNeeded(needDraw, needLayout, animationActive);
159         if (needLayout || animationActive)
160             needDraw = true;
161         long ts = std.datetime.Clock.currStdTime;
162         if (animationActive && lastDrawTs != 0) {
163             animate(ts - lastDrawTs);
164             // layout required flag could be changed during animate - check again
165             checkUpdateNeeded(needDraw, needLayout, animationActive);
166         }
167         if (needLayout) {
168             long measureStart = currentTimeMillis;
169             measure();
170             long measureEnd = currentTimeMillis;
171             Log.d("measure took ", measureEnd - measureStart, " ms");
172             layout();
173             long layoutEnd = currentTimeMillis;
174             Log.d("layout took ", layoutEnd - measureEnd, " ms");
175             //checkUpdateNeeded(needDraw, needLayout, animationActive);
176         }
177         long drawStart = currentTimeMillis;
178         // draw main widget
179         _mainWidget.onDraw(buf);
180         // draw popups
181         foreach(p; _popups)
182             p.onDraw(buf);
183         long drawEnd = currentTimeMillis;
184         Log.d("draw took ", drawEnd - drawStart, " ms");
185         lastDrawTs = ts;
186         if (animationActive)
187             scheduleAnimation();
188     }
189 
190     /// after drawing, call to schedule redraw if animation is active
191     void scheduleAnimation() {
192         // override if necessary
193     }
194 
195 
196     protected void setCaptureWidget(Widget w, MouseEvent event) {
197         _mouseCaptureWidget = w;
198         _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
199     }
200 
201     protected Widget _focusedWidget;
202     /// returns current focused widget
203     @property Widget focusedWidget() { 
204         if (!isChild(_focusedWidget))
205             _focusedWidget = null;
206         return _focusedWidget; 
207     }
208 
209     /// change focus to widget
210     Widget setFocus(Widget newFocus) {
211         if (!isChild(_focusedWidget))
212             _focusedWidget = null;
213         Widget oldFocus = _focusedWidget;
214         if (oldFocus is newFocus)
215             return oldFocus;
216         if (oldFocus !is null)
217             oldFocus.resetState(State.Focused);
218         if (newFocus is null || isChild(newFocus)) {
219             _focusedWidget = newFocus;
220             if (_focusedWidget !is null) {
221                 Log.d("new focus: ", _focusedWidget.id);
222                 _focusedWidget.setState(State.Focused);
223             }
224         }
225         return _focusedWidget;
226     }
227 
228     protected bool dispatchKeyEvent(Widget root, KeyEvent event) {
229         return false;
230     }
231 
232     /// dispatch keyboard event
233     bool dispatchKeyEvent(KeyEvent event) {
234         Widget focus = focusedWidget;
235         if (focus !is null) {
236             if (focus.onKeyEvent(event))
237                 return true; // processed by focused widget
238         }
239         return false;
240     }
241 
242     protected bool dispatchMouseEvent(Widget root, MouseEvent event) {
243         // only route mouse events to visible widgets
244         if (root.visibility != Visibility.Visible)
245             return false;
246         if (!root.isPointInside(event.x, event.y))
247             return false;
248         // offer event to children first
249         for (int i = 0; i < root.childCount; i++) {
250             Widget child = root.child(i);
251             if (dispatchMouseEvent(child, event))
252                 return true;
253         }
254         // if not processed by children, offer event to root
255         if (sendAndCheckOverride(root, event)) {
256             Log.d("MouseEvent is processed");
257             if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null && !event.doNotTrackButtonDown) {
258                 Log.d("Setting active widget");
259                 setCaptureWidget(root, event);
260             } else if (event.action == MouseAction.Move) {
261                 addTracking(root);
262             }
263             return true;
264         }
265         return false;
266     }
267 
268     /// widget which tracks Move events
269     //protected Widget _mouseTrackingWidget;
270     protected Widget[] _mouseTrackingWidgets;
271     private void addTracking(Widget w) {
272         for(int i = 0; i < _mouseTrackingWidgets.length; i++)
273             if (w is _mouseTrackingWidgets[i])
274                 return;
275         //foreach(widget; _mouseTrackingWidgets)
276         //    if (widget is w)
277         //       return;
278         //Log.d("addTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
279         _mouseTrackingWidgets ~= w;
280         //Log.d("addTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
281     }
282     private bool checkRemoveTracking(MouseEvent event) {
283         import std.algorithm;
284         bool res = false;
285         for(int i = cast(int)_mouseTrackingWidgets.length - 1; i >=0; i--) {
286             Widget w = _mouseTrackingWidgets[i];
287             if (!isChild(w)) {
288                 // std.algorithm.remove does not work for me
289                 //_mouseTrackingWidgets.remove(i);
290                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
291                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
292                 _mouseTrackingWidgets.length--;
293                 continue;
294             }
295             if (event.action == MouseAction.Leave || !w.isPointInside(event.x, event.y)) {
296                 // send Leave message
297                 MouseEvent leaveEvent = new MouseEvent(event);
298                 leaveEvent.changeAction(MouseAction.Leave);
299                 res = w.onMouseEvent(leaveEvent) || res;
300                 // std.algorithm.remove does not work for me
301                 //Log.d("removeTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
302                 //_mouseTrackingWidgets.remove(i);
303                 //_mouseTrackingWidgets.length--;
304                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
305                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
306                 _mouseTrackingWidgets.length--;
307                 //Log.d("removeTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
308             }
309         }
310         return res;
311     }
312 
313     /// widget which tracks all events after processed ButtonDown
314     protected Widget _mouseCaptureWidget;
315 	protected ushort _mouseCaptureButtons;
316     protected bool _mouseCaptureFocusedOut;
317 	/// does current capture widget want to receive move events even if pointer left it
318     protected bool _mouseCaptureFocusedOutTrackMovements;
319 	
320 	protected bool dispatchCancel(MouseEvent event) {
321     	event.changeAction(MouseAction.Cancel);
322         bool res = _mouseCaptureWidget.onMouseEvent(event);
323 		_mouseCaptureWidget = null;
324 		_mouseCaptureFocusedOut = false;
325 		return res;
326 	}
327 	
328     protected bool sendAndCheckOverride(Widget widget, MouseEvent event) {
329 		if (!isChild(widget))
330 			return false;
331         bool res = widget.onMouseEvent(event);
332         if (event.trackingWidget !is null && _mouseCaptureWidget !is event.trackingWidget) {
333             setCaptureWidget(event.trackingWidget, event);
334         }
335         return res;
336     }
337 
338     /// dispatch mouse event to window content widgets
339     bool dispatchMouseEvent(MouseEvent event) {
340         // ignore events if there is no root
341         if (_mainWidget is null)
342             return false;
343 
344         // check if _mouseCaptureWidget and _mouseTrackingWidget still exist in child of root widget
345         if (_mouseCaptureWidget !is null && !isChild(_mouseCaptureWidget))
346             _mouseCaptureWidget = null;
347 
348         //Log.d("dispatchMouseEvent ", event.action, "  (", event.x, ",", event.y, ")");
349 
350         bool res = false;
351 		ushort currentButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
352         if (_mouseCaptureWidget !is null) {
353             // try to forward message directly to active widget
354             if (event.action == MouseAction.Move) {
355                 if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
356 					if (currentButtons != _mouseCaptureButtons)
357 						return dispatchCancel(event);
358                     // point is no more inside of captured widget
359                     if (!_mouseCaptureFocusedOut) {
360                         // sending FocusOut message
361                         event.changeAction(MouseAction.FocusOut);
362                         _mouseCaptureFocusedOut = true;
363 						_mouseCaptureButtons = currentButtons;
364                         _mouseCaptureFocusedOutTrackMovements = sendAndCheckOverride(_mouseCaptureWidget, event);
365                         return true;
366                     } else if (_mouseCaptureFocusedOutTrackMovements) {
367 						// pointer is outside, but we still need to track pointer
368                         return sendAndCheckOverride(_mouseCaptureWidget, event);
369                     }
370 					// don't forward message
371                     return true;
372                 } else {
373                     // point is inside widget
374                     if (_mouseCaptureFocusedOut) {
375                         _mouseCaptureFocusedOut = false;
376 						if (currentButtons != _mouseCaptureButtons)
377 							return dispatchCancel(event);
378                        	event.changeAction(MouseAction.FocusIn); // back in after focus out
379                     }
380                     return sendAndCheckOverride(_mouseCaptureWidget, event);
381                 }
382             } else if (event.action == MouseAction.Leave) {
383                 if (!_mouseCaptureFocusedOut) {
384                     // sending FocusOut message
385                     event.changeAction(MouseAction.FocusOut);
386                     _mouseCaptureFocusedOut = true;
387 					_mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
388                     return sendAndCheckOverride(_mouseCaptureWidget, event);
389                 }
390                 return true;
391             }
392             // other messages
393             res = sendAndCheckOverride(_mouseCaptureWidget, event);
394             if (!currentButtons) {
395                 // usable capturing - no more buttons pressed
396                 Log.d("unsetting active widget");
397                 _mouseCaptureWidget = null;
398             }
399             return res;
400         }
401         bool processed = false;
402         if (event.action == MouseAction.Move || event.action == MouseAction.Leave) {
403             processed = checkRemoveTracking(event);
404         }
405         if (!res) {
406             bool insideOneOfPopups = false;
407             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
408 				auto p = _popups[i];
409                 if (p.isPointInside(event.x, event.y))
410                     insideOneOfPopups = true;
411             }
412             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
413 				auto p = _popups[i];
414                 if (!insideOneOfPopups) {
415                     if (p.onMouseEventOutside(event)) // stop loop when true is returned, but allow other main widget to handle event
416                         break;
417                 } else {
418                     if (dispatchMouseEvent(p, event))
419                         return true;
420                 }
421             }
422             res = dispatchMouseEvent(_mainWidget, event);
423         }
424         return res || processed || _mainWidget.needDraw;
425     }
426 
427     /// checks content widgets for necessary redraw and/or layout
428     protected void checkUpdateNeeded(Widget root, ref bool needDraw, ref bool needLayout, ref bool animationActive) {
429         if (root is null)
430             return;
431         if (!root.visibility == Visibility.Visible)
432             return;
433         needDraw = root.needDraw || needDraw;
434         if (!needLayout) {
435             needLayout = root.needLayout || needLayout;
436             if (needLayout) {
437                 Log.d("need layout: ", root.id);
438             }
439         }
440         animationActive = root.animating || animationActive;
441         for (int i = 0; i < root.childCount; i++)
442             checkUpdateNeeded(root.child(i), needDraw, needLayout, animationActive);
443     }
444     /// checks content widgets for necessary redraw and/or layout
445     bool checkUpdateNeeded(ref bool needDraw, ref bool needLayout, ref bool animationActive) {
446         needDraw = needLayout = animationActive = false;
447         if (_mainWidget is null)
448             return false;
449         checkUpdateNeeded(_mainWidget, needDraw, needLayout, animationActive);
450         foreach(p; _popups)
451             checkUpdateNeeded(p, needDraw, needLayout, animationActive);
452         return needDraw || needLayout || animationActive;
453     }
454     /// requests update for window (unless force is true, update will be performed only if layout, redraw or animation is required).
455     void update(bool force = false) {
456         if (_mainWidget is null)
457             return;
458         bool needDraw = false;
459         bool needLayout = false;
460         bool animationActive = false;
461         if (checkUpdateNeeded(needDraw, needLayout, animationActive) || force) {
462             Log.d("Requesting update");
463             invalidate();
464         }
465         Log.d("checkUpdateNeeded returned needDraw=", needDraw, " needLayout=", needLayout, " animationActive=", animationActive);
466     }
467     /// request window redraw
468     abstract void invalidate();
469 }
470 
471 class Platform {
472     static __gshared Platform _instance;
473     static void setInstance(Platform instance) {
474         _instance = instance;
475     }
476     @property static Platform instance() {
477         return _instance;
478     }
479     abstract Window createWindow(string windowCaption, Window parent);
480     abstract int enterMessageLoop();
481 }
482 
483 version (USE_OPENGL) {
484     private __gshared bool _OPENGL_ENABLED = false;
485     /// check if hardware acceleration is enabled
486     @property bool openglEnabled() { return _OPENGL_ENABLED; }
487     /// call on app initialization if OpenGL support is detected
488     void setOpenglEnabled() {
489         _OPENGL_ENABLED = true;
490 	    glyphDestroyCallback = &onGlyphDestroyedCallback;
491     }
492 }
493 
494 
495 /// put "mixin APP_ENTRY_POINT;" to main module of your dlangui based app
496 mixin template APP_ENTRY_POINT() {
497     version (linux) {
498 	    //pragma(lib, "png");
499 	    pragma(lib, "xcb");
500 	    pragma(lib, "xcb-shm");
501 	    pragma(lib, "xcb-image");
502 	    pragma(lib, "X11-xcb");
503 	    pragma(lib, "X11");
504 	    pragma(lib, "dl");
505     }
506 
507     /// workaround for link issue when WinMain is located in library
508     version(Windows) {
509         private import win32.windows;
510         private import dlangui.platforms.windows.winapp;
511         extern (Windows)
512             int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
513                         LPSTR lpCmdLine, int nCmdShow)
514             {
515                 return DLANGUIWinMain(hInstance, hPrevInstance,
516                                       lpCmdLine, nCmdShow);
517             }
518     }
519 }