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 }