1 module awebview.gui.activity; 2 3 import carbon.utils; 4 5 import awebview.wrapper.websession, 6 awebview.wrapper.webview, 7 awebview.wrapper.webcore, 8 awebview.wrapper.webstring, 9 awebview.wrapper.weburl, 10 awebview.wrapper.weakref, 11 awebview.wrapper.sys, 12 awebview.wrapper.cpp : NativeWindow; 13 14 import awebview.gui.html, 15 awebview.gui.application, 16 awebview.gui.methodhandler, 17 derelict.sdl2.sdl; 18 19 import awebview.gui.menulistener; 20 import awebview.gui.viewlistener; 21 import awebview.gui.scripthelper; 22 23 import awebview.cssgrammar; 24 25 import std.exception, 26 std..string; 27 28 29 //version = AwebviewSaveHTML; 30 31 interface ActivityFactory(A : Activity) 32 { 33 Activity newInstance() @property; 34 } 35 36 interface ActivityFactory(A) : ActivityFactory!Activity 37 if(is(A : Activity)) 38 { 39 A newInstance() @property; 40 } 41 42 43 class Activity 44 { 45 this(string id, uint width, uint height, WebView view) 46 { 47 _id = id; 48 _width = width; 49 _height = height; 50 _view = view; 51 _view.loadURL(WebURL(`data:text/html,<h1></h1>`)); 52 WebCore.instance.update(); 53 createObject("_carrierObject_"); 54 _methodHandler = new MethodHandler(); 55 _view.jsMethodHandler = _methodHandler; 56 } 57 58 59 void onStart(Application app) 60 { 61 _app = app; 62 63 auto ml = new MenuListener(_app); 64 _view.setMenuListener(ml.cppObj); 65 66 auto vl = new ViewListener(_app); 67 _view.setViewListener(vl.cppObj); 68 69 _scriptHelper = createScriptHelper(this); 70 71 foreach(k, p; _pages.maybeModified) 72 p.page.onStart(this); 73 74 if(_nowPage !is null){ 75 string loadId = _nowPage.id; 76 _nowPage = null; // avoid onDetach() 77 this.load(loadId); 78 } 79 } 80 81 82 void attach() 83 { 84 application.attachActivity(this.id); 85 } 86 87 88 void onAttach() 89 { 90 foreach(k, e; _children.maybeModified) 91 if(e.isDetached) 92 e.attach(); 93 94 _isAttached = true; 95 } 96 97 98 void onUpdate() 99 { 100 foreach(k, e; _children.maybeModified) 101 if(e.isShouldClosed) 102 _children.remove(k); 103 104 _nowPage.onUpdate(); 105 } 106 107 108 void detach() 109 { 110 application.detachActivity(this.id); 111 } 112 113 114 void onDetach() 115 { 116 foreach(k, e; _children.maybeModified) 117 if(e.isAttached) 118 e.detach(); 119 120 _isAttached = false; 121 } 122 123 124 void onDestroy() 125 { 126 foreach(k, e; _children.maybeModified) 127 e.close(); 128 129 foreach(k, p; _pages.maybeModified){ 130 p.page.onDetach(); 131 p.page.onDestroy(); 132 } 133 134 releaseObject("_carrierObject_"); 135 _view.destroy(); 136 } 137 138 139 final 140 void close() 141 { 142 _isShouldClosed = true; 143 } 144 145 146 final 147 @property 148 bool isAttached() const pure nothrow @safe @nogc { return _isAttached; } 149 150 151 final 152 @property 153 bool isDetached() const pure nothrow @safe @nogc { return !_isAttached; } 154 155 156 final 157 @property 158 inout(Application) application() inout pure nothrow @safe @nogc { return _app; } 159 160 161 final 162 @property 163 string id() const pure nothrow @safe @nogc { return _id; } 164 165 166 final 167 @property 168 inout(WebView) view() inout pure nothrow @safe @nogc 169 { 170 return _view; 171 } 172 173 174 @property 175 inout(MethodHandler) methodHandler() inout pure nothrow @safe @nogc 176 { 177 return _methodHandler; 178 } 179 180 181 @property 182 size_t width() const pure nothrow @safe @nogc 183 { 184 return _width; 185 } 186 187 188 @property 189 void width(uint w) 190 { 191 _width = w; 192 this.resize(_width, _height); 193 } 194 195 196 @property 197 size_t height() const pure nothrow @safe @nogc 198 { 199 return _height; 200 } 201 202 203 @property 204 void height(uint h) 205 { 206 _height = h; 207 this.resize(_width, _height); 208 } 209 210 211 void resize(uint w, uint h) 212 { 213 this._view.resize(w, h); 214 _nowPage.onResize(w, h); 215 } 216 217 218 void addPage(HTMLPage page) 219 { 220 _pages[page.id] = PageType(page, false); 221 222 if(_app !is null && _app.isRunning) 223 page.onStart(this); 224 } 225 226 227 void opOpAssign(string op : "~")(HTMLPage page) 228 { 229 addPage(page); 230 } 231 232 233 final 234 HTMLPage opIndex(string id) 235 { 236 return _pages[id].page; 237 } 238 239 240 final 241 auto opIndex(Dollar.QuerySelector dollar) 242 { 243 return .querySelector(this, dollar.selector); 244 } 245 246 247 final 248 Dollar opDollar() pure nothrow @safe @nogc { return Dollar.init; } 249 250 251 static struct Dollar 252 { 253 static struct QuerySelector 254 { 255 string selector; 256 } 257 258 259 QuerySelector QS(string str) pure nothrow @safe @nogc 260 { 261 return QuerySelector(str); 262 } 263 264 265 alias opCall = QS; 266 } 267 268 269 void load(string id) 270 { 271 import std.stdio; 272 immutable bool isStarted = this.application !is null && this.application.isRunning; 273 274 if(_nowPage !is null && _nowPage.id == id){ 275 reload(); 276 return; 277 } 278 279 auto p = enforce(id in _pages); 280 281 if(_nowPage !is null && isStarted) 282 _nowPage.onDetach(); 283 284 _nowPage = p.page; 285 if(isStarted) 286 { 287 bool bInit; 288 if(!p.wasLoaded){ 289 p.wasLoaded = true; 290 bInit = true; 291 } 292 293 _nowPage.onAttach(bInit); 294 _loadImpl(bInit); 295 } 296 } 297 298 299 void load(HTMLPage page) 300 { 301 if(page.id !in _pages || page !is _pages[page.id].page) 302 addPage(page); 303 304 this.load(page.id); 305 } 306 307 308 void reload() 309 in { 310 assert(this.nowPage !is null); 311 } 312 body { 313 _loadImpl(false); 314 } 315 316 317 private void _loadImpl(bool isInit) 318 { 319 import std.stdio; 320 import std.uri : encodeComponent; 321 322 // save html to disk 323 import std.path : buildPath; 324 import std.file; 325 326 immutable htmlPath = buildPath(application.exeDir, "Activity-" ~ this.id ~ "-HTMLPage-" ~ nowPage.id ~ ".html"); 327 328 version(AwebviewSaveHTML) 329 { 330 try 331 std.file.write(htmlPath, _nowPage.html); 332 catch(Exception){} 333 } 334 335 _view.loadURL(WebURL(htmlPath)); 336 while(_view.isLoading) 337 WebCore.instance.update(); 338 339 _nowPage.onLoad(isInit); 340 } 341 342 343 @property 344 inout(HTMLPage) nowPage() inout pure nothrow @safe @nogc 345 { 346 return _nowPage; 347 } 348 349 350 final 351 WeakRef!JSObject createObject(string name) 352 { 353 WebString str = name; 354 JSValue v = _view.createGlobalJSObject(str); 355 assert(v.isObject); 356 _objects[name] = v; 357 return _objects[name].get!(WeakRef!JSObject); 358 } 359 360 361 final 362 void runJS(string script) 363 { 364 _view.executeJS(script, ""); 365 } 366 367 368 final 369 JSValue evalJS(string script) 370 { 371 return _view.executeJSWithRV(script, ""); 372 } 373 374 375 final 376 void releaseObject(string name) 377 { 378 import carbon.templates : Lstr; 379 380 if(auto p = name in _objects){ 381 runJS(mixin(Lstr!q{%[name%] = null; delete %[name%];})); 382 _objects.remove(name); 383 } 384 } 385 386 387 final 388 @property 389 inout(JSValue[string]) objects() inout 390 { 391 return _objects; 392 } 393 394 395 final 396 auto getObject(string name) 397 { 398 return this._objects[name].get!(WeakRef!JSObject); 399 } 400 401 402 final 403 @property 404 auto carrierObject() 405 { 406 return getObject("_carrierObject_"); 407 } 408 409 410 @property 411 bool isShouldClosed() { return _isShouldClosed; } 412 413 414 final 415 void addChild(Activity act) 416 { 417 _children[act.id] = act; 418 } 419 420 421 final 422 void removeChild(string id) 423 { 424 _children.remove(id); 425 } 426 427 428 final 429 @property 430 inout(Activity[string]) children() inout 431 { 432 return _children; 433 } 434 435 436 void onReceiveImmediateMessage(ImmediateMessage msg) 437 { 438 foreach(k, e; _pages.maybeModified){ 439 e.page.onReceiveImmediateMessage(msg); 440 } 441 } 442 443 444 private: 445 Application _app; 446 string _id; 447 uint _width; 448 uint _height; 449 WebView _view; 450 HTMLPage _nowPage; 451 MethodHandler _methodHandler; 452 ScriptHelper _scriptHelper; 453 JSValue[string] _objects; 454 bool _isAttached; 455 bool _isShouldClosed; 456 457 Activity[string] _children; 458 459 struct PageType { HTMLPage page; bool wasLoaded; } 460 PageType[string] _pages; 461 } 462 463 464 class SDLActivity : Activity 465 { 466 this(string id, uint width, uint height, string title, WebSession session = null, uint sdlFlags = SDL_WINDOW_RESIZABLE) 467 { 468 import std..string : toStringz; 469 import std.exception : enforce; 470 471 _sdlWind = enforce(SDL_CreateWindow(toStringz(title), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, sdlFlags | SDL_WINDOW_HIDDEN)); 472 473 auto view = WebCore.instance.createWebView(width, height, session, WebViewType.window); 474 475 version(Windows) 476 { 477 /* see https://wiki.libsdl.org/SDL_SysWMinfo */ 478 SDL_SysWMinfo wmi; 479 SDL_VERSION(&(wmi.version_)); 480 481 if(SDL_GetWindowWMInfo(_sdlWind, &wmi)) 482 view.parentWindow = wmi.info.win.window; 483 } 484 super(id, width, height, view); 485 486 _winds[_sdlWind] = this; 487 } 488 489 490 static 491 auto factory() @property 492 { 493 static final class Factory : ActivityFactory!SDLActivity 494 { 495 string id = null; 496 uint width = 640; 497 uint height = 480; 498 string title = ""; 499 WebSession webSession = null; 500 uint sdlFlag = SDL_WINDOW_RESIZABLE; 501 502 SDLActivity newInstance() @property 503 { 504 enforce(id !is null, "id is null"); 505 506 return new SDLActivity(id, width, height, title, webSession, sdlFlag); 507 } 508 } 509 510 511 return new Factory(); 512 } 513 514 515 override 516 void onAttach() 517 { 518 super.onAttach(); 519 SDL_ShowWindow(_sdlWind); 520 } 521 522 523 override 524 void onDetach() 525 { 526 super.onDetach(); 527 SDL_HideWindow(_sdlWind); 528 } 529 530 531 override 532 void onDestroy() 533 { 534 _winds.remove(_sdlWind); 535 SDL_DestroyWindow(_sdlWind); 536 537 super.onDestroy(); 538 } 539 540 541 final 542 @property 543 SDL_Window* sdlWindow() 544 { 545 return _sdlWind; 546 } 547 548 549 final 550 @property 551 uint windowID() 552 { 553 return SDL_GetWindowID(_sdlWind); 554 } 555 556 557 void title(string t) 558 { 559 import std..string : toStringz; 560 SDL_SetWindowTitle(_sdlWind, toStringz(t)); 561 } 562 563 564 override 565 void onUpdate() 566 { 567 super.onUpdate(); 568 SDL_GL_SwapWindow(_sdlWind); 569 } 570 571 572 final 573 @property 574 bool hasKeyFocus() 575 { 576 return SDL_GetKeyboardFocus() == _sdlWind; 577 } 578 579 580 @property 581 bool isActive() 582 { 583 bool b = this.hasKeyFocus(); 584 foreach(id, e; children) 585 if(auto sdlAct = cast(SDLActivity)e) 586 b = b || sdlAct.isActive(); 587 588 return b; 589 } 590 591 592 void onSDLEvent(const SDL_Event* event) 593 { 594 if(event.type == SDL_WINDOWEVENT 595 && event.window.event == SDL_WINDOWEVENT_RESIZED 596 && event.window.windowID == this.windowID) 597 { 598 this.resize(event.window.data1, event.window.data2); 599 return; 600 } 601 602 if(event.type == SDL_WINDOWEVENT 603 && event.window.event == SDL_WINDOWEVENT_CLOSE 604 && event.window.windowID == this.windowID) 605 { 606 this.close(); 607 return; 608 } 609 } 610 611 612 private: 613 SDL_Window* _sdlWind; 614 615 static SDLActivity[SDL_Window*] _winds; 616 } 617 618 619 version(Windows) 620 { 621 import core.sys.windows.com; 622 import core.sys.windows.windows; 623 624 extern(Windows) nothrow export @nogc 625 { 626 LONG SetWindowLongW(HWND,int,LONG); 627 BOOL MoveWindow( 628 HWND hWnd, // ウィンドウのハンドル 629 int X, // 横方向の位置 630 int Y, // 縦方向の位置 631 int nWidth, // 幅 632 int nHeight, // 高さ 633 BOOL bRepaint // 再描画オプション 634 ); 635 } 636 637 extern (C) 638 { 639 extern CLSID CLSID_TaskbarList; 640 } 641 642 extern(C) 643 { 644 extern IID IID_ITaskbarList; 645 } 646 647 extern(System) 648 interface ITaskbarList : IUnknown 649 { 650 HRESULT HrInit(); 651 void unusedAddTab(); 652 HRESULT DeleteTab(HWND hwnd); 653 HRESULT unusedActivateTab(); 654 HRESULT unusedSetActivateAlt(); 655 } 656 657 void deleteFromTaskbar(HWND hwnd) 658 { 659 ITaskbarList tbl; 660 CoCreateInstance(&CLSID_TaskbarList, 661 null, 662 CLSCTX_INPROC_SERVER, 663 &IID_ITaskbarList, 664 cast(void*)&tbl); 665 tbl.HrInit(); 666 tbl.DeleteTab(hwnd); 667 } 668 } 669 670 671 class SDLBorderlessActivity : SDLActivity 672 { 673 this(string id, uint width, uint height, string title, WebSession session = null, uint orSDLFlags = SDL_WINDOW_RESIZABLE) 674 { 675 uint flags = orSDLFlags | SDL_WINDOW_BORDERLESS; 676 super(id, width, height, title, session, flags); 677 678 version(Windows) 679 { 680 enum int GWL_STYLE = -16; 681 enum LONG WS_POPUP = 0x80000000; 682 683 /* see https://wiki.libsdl.org/SDL_SysWMinfo */ 684 SDL_SysWMinfo wmi; 685 SDL_VERSION(&(wmi.version_)); 686 687 if(SDL_GetWindowWMInfo(_sdlWind, &wmi)) 688 SetWindowLongW(wmi.info.win.window, GWL_STYLE, WS_POPUP); 689 } 690 } 691 692 693 static 694 auto factory() @property 695 { 696 static final class Factory : ActivityFactory!SDLBorderlessActivity 697 { 698 string id = null; 699 uint width = 640; 700 uint height = 480; 701 string title = ""; 702 WebSession webSession = null; 703 uint sdlFlag = SDL_WINDOW_RESIZABLE; 704 705 SDLBorderlessActivity newInstance() @property 706 { 707 enforce(id !is null, "id is null"); 708 709 return new SDLBorderlessActivity(id, width, height, title, webSession, sdlFlag); 710 } 711 } 712 713 714 return new Factory(); 715 } 716 717 718 override 719 void resize(uint w, uint h) 720 { 721 SDL_SetWindowSize(this.sdlWindow, w, h); 722 super.resize(w, h); 723 } 724 } 725 726 727 class SDLPopupActivity : SDLBorderlessActivity 728 { 729 this(size_t idx, WebSession session = null, uint orSDLFlags = SDL_WINDOW_RESIZABLE) 730 { 731 super(format("_PopupActivity%s_", idx), 0, 0, "", session, orSDLFlags); 732 _idx = idx; 733 _session = session; 734 _flags = orSDLFlags; 735 736 enforce(_popupActivities.length == idx); 737 _popupActivities ~= this; 738 739 version(Windows) 740 { 741 /* see https://wiki.libsdl.org/SDL_SysWMinfo */ 742 SDL_SysWMinfo wmi; 743 SDL_VERSION(&(wmi.version_)); 744 745 if(SDL_GetWindowWMInfo(_sdlWind, &wmi)) 746 deleteFromTaskbar(wmi.info.win.window); 747 } 748 } 749 750 751 static 752 auto factory() @property 753 { 754 static final class Factory : ActivityFactory!SDLPopupActivity 755 { 756 size_t index; 757 WebSession webSession = null; 758 uint sdlFlag = SDL_WINDOW_RESIZABLE; 759 760 SDLPopupActivity newInstance() @property 761 { 762 return new SDLPopupActivity(index, webSession, sdlFlag); 763 } 764 } 765 766 767 return new Factory(); 768 } 769 770 771 void popup(HTMLPage page, SDLActivity activity, int x, int y, uint w = 0, uint h = 0) 772 { 773 activity.addChild(this); 774 _parent = activity; 775 SDL_SetWindowPosition(this.sdlWindow, x, y); 776 this.attach(); 777 SDL_RaiseWindow(this.sdlWindow); 778 this.load(page); 779 780 _wfitting = w == 0; 781 _hfitting = h == 0; 782 783 if(!_wfitting && !_hfitting) 784 this.resize(w, h); 785 786 _x = x; 787 _y = y; 788 _w = w; 789 _h = h; 790 } 791 792 793 void popupAtRel(HTMLPage page, SDLActivity activity, int relX, int relY, uint w = 0, uint h = 0) 794 { 795 int x, y; 796 SDL_GetWindowPosition(activity.sdlWindow, &x, &y); 797 798 x += relX; 799 y += relY; 800 popup(page, activity, x, y, w, h); 801 } 802 803 804 void popup(HTMLPage page, SDLActivity activity, uint w = 0, uint h = 0) 805 { 806 version(Windows) 807 { 808 import core.sys.windows.windows; 809 POINT p; 810 GetCursorPos(&p); 811 812 popup(page, activity, p.x, p.y, w, h); 813 } 814 else 815 { 816 SDL_PumpEvents(); 817 int dx, dy; 818 SDL_GetMouseState(&dx, &dy); 819 820 int x, y; 821 SDL_GetWindowPosition(activity.sdlWindow, &x, &y); 822 823 x += dx; 824 y += dy; 825 826 popup(page, activity, x, y, w, h); 827 } 828 } 829 830 831 void popupChild(HTMLPage page, int relX, int relY, uint w = 0, uint h = 0) 832 { 833 popupChildAtAbs(page, _x + relX, _y + relY, w, h); 834 } 835 836 837 void popupChildAtAbs(HTMLPage page, uint x, uint y, uint w = 0, uint h = 0) 838 { 839 auto child = childPopup(); 840 if(child is null){ 841 child = new SDLPopupActivity(_idx + 1, _session, _flags); 842 this.application.addActivity(child); 843 } 844 845 child.popup(page, this, x, y, w, h); 846 } 847 848 849 void popupChildRight(HTMLPage page, int relY, uint w = 0, uint h = 0) 850 { 851 popupChild(page, _w, relY, w, h); 852 } 853 854 855 void popupChildButtom(HTMLPage page, int relX, uint w = 0, uint h = 0) 856 { 857 popupChild(page, relX, _h, w, h); 858 } 859 860 861 @property 862 SDLPopupActivity childPopup() 863 { 864 if(_popupActivities.length > _idx+1) 865 return _popupActivities[_idx+1]; 866 else 867 return null; 868 } 869 870 871 override 872 void onDetach() 873 { 874 _x = 0; 875 _y = 0; 876 _w = 0; 877 _h = 0; 878 _wfitting = false; 879 _hfitting = false; 880 this.resize(0, 0); 881 882 if(_parent){ 883 _parent.removeChild(this.id); 884 _parent = null; 885 } 886 887 super.onDetach(); 888 } 889 890 891 override 892 void onUpdate() 893 { 894 super.onUpdate(); 895 896 if(_wfitting || _hfitting){ 897 uint sw = _w, 898 sh = _h; 899 900 if(_wfitting) 901 sw = evalJS(q{document.documentElement.scrollWidth}).get!uint; 902 903 if(_hfitting) 904 sh = evalJS(q{document.documentElement.scrollHeight}).get!uint; 905 906 if(_w != sw || _h != sh){ 907 this.resize(sw, sh); 908 _w = sw; 909 _h = sh; 910 } 911 } 912 913 if(_parent.isDetached || !this.isActive){ 914 this.detach(); 915 } 916 } 917 918 919 override 920 void onSDLEvent(const SDL_Event* event) 921 { 922 // ignore resized by user 923 if(event.type == SDL_WINDOWEVENT 924 && event.window.event == SDL_WINDOWEVENT_RESIZED 925 && event.window.windowID == this.windowID) 926 { 927 this.resize(_w, _h); 928 return; 929 } 930 931 super.onSDLEvent(event); 932 } 933 934 935 private: 936 size_t _idx; 937 WebSession _session; 938 uint _flags; 939 940 bool _wfitting, _hfitting; 941 uint _x, _y; 942 uint _w, _h; 943 944 SDLActivity _parent; 945 946 static SDLPopupActivity[] _popupActivities; 947 }