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