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 }