1 module awebview.gui.application;
2 
3 import std.exception;
4 import std.file;
5 
6 import awebview.wrapper.webcore;
7 import awebview.gui.resourceinterceptor;
8 
9 import awebview.gui.activity;
10 import awebview.gui.html;
11 import derelict.sdl2.sdl;
12 import msgpack;
13 import carbon.utils;
14 
15 import core.thread;
16 
17 
18 abstract class Application
19 {
20     this(string savedFileName)
21     {
22         _savedFileName = savedFileName;
23         if(exists(savedFileName))
24             _savedData = unpack!(ubyte[][string])(cast(ubyte[])std.file.read(savedFileName));
25     }
26 
27 
28     void onDestroy()
29     {
30         if(_savedData.length)
31             std.file.write(_savedFileName, pack(_savedData));
32     }
33 
34 
35     final
36     @property
37     ref ubyte[][string] savedData() pure nothrow @safe @nogc { return _savedData; }
38 
39     void addActivity(Activity activity);
40 
41     final
42     void opOpAssign(string op : "~")(Activity activity)
43     {
44         addActivity(activity);
45     }
46 
47 
48     bool hasActivity(string id);
49     Activity getActivity(string id);
50 
51     final
52     Activity opIndex(string id) { return getActivity(id); }
53 
54     int opApplyActivities(scope int delegate(Activity activity));
55 
56     void attachActivity(string id);
57     void detachActivity(string id);
58     void destroyActivity(string id);
59 
60     void runAtNextFrame(void delegate());
61 
62     void run();
63     bool isRunning() @property;
64 
65     void shutdown();
66 
67 
68 
69     final
70     @property
71     string exeDir() const
72     {
73         import std.path : dirName;
74         import std.file : thisExePath;
75 
76         return dirName(thisExePath);
77     }
78 
79 
80   private:
81     ubyte[][string] _savedData;
82     string _savedFileName;
83 }
84 
85 
86 class SDLApplication : Application
87 {
88     static immutable savedDataFileName = "saved.mpac";
89 
90     private
91     this()
92     {
93         super(savedDataFileName);
94     }
95 
96 
97     static
98     SDLApplication instance() @property
99     {
100         if(_instance is null){
101             DerelictSDL2.load();
102             enforce(SDL_Init(SDL_INIT_VIDEO) >= 0);
103 
104             _instance = new SDLApplication();
105 
106             auto config = WebConfig();
107             config.additionalOptions ~= "--use-gl=desktop";
108             WebCore.initialize(config);
109             WebCore.instance.resourceInterceptor = new LocalResourceInterceptor(_instance, getcwd());
110         }
111 
112         return _instance;
113     }
114 
115 
116     override
117     void onDestroy()
118     {
119         while(_acts.length){
120             foreach(id, activity; _acts.maybeModified){
121                 activity.onDetach();
122                 activity.onDestroy();
123                 if(_acts[id] is activity)
124                     _acts.remove(id);
125             }
126         }
127 
128         while(_detachedActs.length){
129             foreach(id, activity; _detachedActs.maybeModified){
130                 activity.onDestroy();
131                 if(_detachedActs[id] is activity)
132                     _detachedActs.remove(id);
133             }
134         }
135 
136         super.onDestroy();
137     }
138 
139 
140     A createActivity(A : SDLActivity)(WebPreferences pref, A delegate(WebSession) dg)
141     {
142         auto session = WebCore.instance.createWebSession(WebString(""), pref);
143 
144         auto act = dg(session);
145         addActivity(act);
146 
147         return act;
148     }
149 
150 
151     SDLActivity createActivity(WebPreferences pref, HTMLPage page, string actID, uint width, uint height, string title)
152     {
153         return this.createActivity(pref, delegate(WebSession session){
154             auto act = new SDLActivity(actID, width, height, title, session);
155             act ~= page;
156             act.load(page);
157             return act;
158         });
159     }
160 
161 
162     override
163     void addActivity(Activity act)
164     in {
165       assert(typeid(act) == typeid(SDLActivity));
166     }
167     body {
168         addActivity(cast(SDLActivity)act);
169     }
170 
171 
172     void addActivity(SDLActivity act)
173     in {
174         assert(act !is null);
175     }
176     body {
177         _acts[act.id] = act;
178 
179         if(_isRunning){
180             act.onStart(this);
181             act.onAttach();
182         }
183     }
184 
185 
186     SDLPopupActivity initPopup(WebPreferences pref)
187     {
188         auto act = this.createActivity(pref, delegate(WebSession session){
189             return new SDLPopupActivity(0, session);
190         });
191         this.detachActivity(act.id);
192         _popupRoot = act;
193         return act;
194     }
195 
196 
197     final
198     @property
199     auto activities(this This)() pure nothrow @safe @nogc
200     {
201         static struct Result{
202             auto opIndex(string id) { return _app.getActivity(id); }
203             auto opIndex(uint windowID) { return _app.getActivity(windowID); }
204             auto opIndex(SDL_Window* sdlWindow) { return _app.getActivity(sdlWindow); }
205 
206             auto opBinaryRight(string op : "in")(string id)
207             {
208                 if(auto p = id in _app._acts)
209                     return p;
210                 else if(auto p = id in _app._detachedActs)
211                     return p;
212                 else
213                     return null;
214             }
215 
216           private:
217             This _app;
218         }
219 
220         return Result(this);
221     }
222 
223 
224     final override
225     bool hasActivity(string id)
226     {
227         if(auto p = id in _acts)
228             return true;
229         else if(auto p = id in _detachedActs)
230             return true;
231         else
232             return false;
233     }
234 
235 
236     final
237     SDLActivity getActivity(uint windowID)
238     {
239         foreach(k, a; _acts)
240             if(a.windowID == windowID)
241                 return a;
242 
243         foreach(k, a; _detachedActs)
244             if(a.windowID == windowID)
245                 return a;
246 
247         return null;
248     }
249 
250 
251     final override
252     SDLActivity getActivity(string id)
253     {
254         return _acts.get(id, _detachedActs.get(id, null));
255     }
256 
257 
258     final
259     SDLActivity getActivity(SDL_Window* sdlWind)
260     {
261         foreach(k, a; _acts)
262             if(a.sdlWindow == sdlWind)
263                 return a;
264 
265         foreach(k, a; _detachedActs)
266             if(a.sdlWindow == sdlWind)
267                 return a;
268 
269         return null;
270     }
271 
272 
273     final override
274     int opApplyActivities(scope int delegate(Activity activity) dg)
275     {
276         foreach(k, ref e; _acts)
277             if(auto res = dg(e))
278                 return res;
279 
280         foreach(k, ref e; _detachedActs)
281             if(auto res = dg(e))
282                 return res;
283 
284         return 0;
285     }
286 
287 
288     final
289     @property
290     SDLPopupActivity popupActivity()
291     {
292         return _popupRoot;
293     }
294 
295 
296     override
297     void attachActivity(string id)
298     {
299         if(id in _acts)
300             return;
301 
302         auto act = _detachedActs[id];
303         if(_isRunning) act.onAttach();
304         _detachedActs.remove(id);
305         _acts[id] = act;
306     }
307 
308 
309     override
310     void detachActivity(string id)
311     {
312         if(id in _detachedActs)
313             return;
314 
315         auto act = _acts[id];
316         if(_isRunning) act.onDetach();
317         _acts.remove(id);
318         _detachedActs[id] = act;
319     }
320 
321 
322     override
323     void destroyActivity(string id)
324     {
325         if(auto p = id in _acts){
326             auto act = *p;
327             act.onDetach();
328             act.onDestroy();
329             _acts.remove(id);
330         }else if(auto p = id in _detachedActs){
331             auto act = *p;
332             act.onDestroy();
333             _detachedActs.remove(id);
334         }else
335             enforce(0);
336     }
337 
338 
339     override
340     void runAtNextFrame(void delegate() dg)
341     {
342         _runNextFrame ~= dg;
343     }
344 
345 
346     override
347     void run()
348     {
349         _isRunning = true;
350 
351         auto wc = WebCore.instance;
352         wc.update();
353 
354         foreach(k, a; _acts.maybeModified){
355             a.onStart(this);
356             a.onAttach();
357         }
358 
359         foreach(k, a; _detachedActs.maybeModified){
360             a.onStart(this);
361         }
362 
363       LInf:
364         while(!_isShouldQuit)
365         {
366             foreach(e; _runNextFrame)
367                 e();
368 
369             _runNextFrame.length = 0;
370 
371             {
372                 SDL_Event event;
373                 while(SDL_PollEvent(&event)){
374                     onSDLEvent(&event);
375                     if(_isShouldQuit)
376                         break LInf;
377                 }
378             }
379 
380             foreach(k, a; _acts.maybeModified){
381                 a.onUpdate();
382 
383                 if(a.isShouldClosed)
384                     destroyActivity(a.id);
385 
386                 if(_isShouldQuit)
387                     break LInf;
388             }
389 
390             if(_acts.length == 0)
391                 shutdown();
392 
393             foreach(k, a; _detachedActs.maybeModified){
394                 if(a.isShouldClosed)
395                     destroyActivity(a.id);
396 
397                 if(_isShouldQuit)
398                     break LInf;
399             }
400 
401             Thread.sleep(dur!"msecs"(5));
402             wc.update();
403         }
404         _isRunning = false;
405 
406         shutdown();
407     }
408 
409 
410     override
411     @property
412     bool isRunning() { return _isRunning; }
413 
414 
415     override
416     void shutdown()
417     {
418         if(!_isShouldQuit && _isRunning)
419             _isShouldQuit = true;
420         else{
421             _isRunning = false;
422             _isShouldQuit = true;
423 
424             this.onDestroy();
425 
426             SDL_Quit();
427             WebCore.shutdown();
428         }
429     }
430 
431 
432     void onSDLEvent(const SDL_Event* event)
433     {
434         foreach(k, a; _acts.maybeModified)
435             a.onSDLEvent(event);
436 
437         switch(event.type)
438         {
439           case SDL_QUIT:
440             shutdown();
441             break;
442 
443           default:
444             break;
445         }
446     }
447 
448 
449   private:
450     SDLActivity[string] _acts;
451     SDLActivity[string] _detachedActs;
452     SDLPopupActivity _popupRoot;
453     bool _isRunning;
454     bool _isShouldQuit;
455     ubyte[][string] _savedData;
456 
457     void delegate()[] _runNextFrame;
458 
459     static SDLApplication _instance;
460 }