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 }