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 }