1 /+
2 The MIT License (MIT)
3 
4     Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)>
5 
6     Permission is hereby granted, free of charge, to any person obtaining a copy
7     of this software and associated documentation files (the "Software"), to deal
8     in the Software without restriction, including without limitation the rights
9     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10     copies of the Software, and to permit persons to whom the Software is
11     furnished to do so, subject to the following conditions:
12 
13     The above copyright notice and this permission notice shall be included in
14     all copies or substantial portions of the Software.
15 
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22     THE SOFTWARE.
23 +/
24 
25 module desgui.base.app;
26 
27 import std.conv;
28 
29 import derelict.sdl2.sdl;
30 import derelict.opengl3.gl3;
31 
32 import desgui.core.event;
33 import desgui.core.widget;
34 import desgui.base.glcontext;
35 
36 import desgl;
37 import desil;
38 
39 import desmath.linear.vector;
40 
41 import desutil.signal;
42 import desutil.helpers;
43 
44 import desutil.logger;
45 mixin( PrivateLoggerMixin );
46 
47 class DiAppException: Exception 
48 { @safe pure nothrow this( string msg, string file=__FILE__, int line=__LINE__ ){ super( msg, file, line ); } }
49 
50 class DiAppWindow
51 {
52 package:
53     SDL_Window *window = null;
54     SDL_GLContext context;
55     DiWidget widget;
56     void delegate( in irect ) setClear;
57 
58     ivec2 mpos;
59 
60     void makeCurrent()
61     {
62         // TODO: add checking of current window and context
63         if( SDL_GL_MakeCurrent( window, context ) < 0 )
64             throw new DiAppException( "SDL fails with make current: " ~ toDString( SDL_GetError() ) );
65     }
66 
67     DiGLDrawStack gldrawstack;
68 
69     void process()
70     {
71         makeCurrent();
72 
73         widget.idle();
74 
75         setClear( widget.rect );
76         glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
77         widget.draw();
78 
79         SDL_GL_SwapWindow( window );
80     }
81 
82     void window_eh( in SDL_WindowEvent ev )
83     {
84         makeCurrent();
85 
86         switch( ev.event ) 
87         {
88             case SDL_WINDOWEVENT_NONE:         break;
89             case SDL_WINDOWEVENT_SHOWN:        
90                 widget.activate();
91                 break;
92             case SDL_WINDOWEVENT_HIDDEN:       
93                 widget.release();
94                 break;
95             case SDL_WINDOWEVENT_EXPOSED:      break;
96             case SDL_WINDOWEVENT_MOVED:        break;
97             case SDL_WINDOWEVENT_RESIZED:      break;
98             case SDL_WINDOWEVENT_SIZE_CHANGED: 
99                 auto rr = irect( ivec2(0,0), ivec2(ev.data1,ev.data2) );
100                 widget.reshape( rr ); 
101                 break;
102             case SDL_WINDOWEVENT_MINIMIZED:    break;
103             case SDL_WINDOWEVENT_MAXIMIZED:    break;
104             case SDL_WINDOWEVENT_RESTORED:     break;
105             case SDL_WINDOWEVENT_ENTER:        
106                 widget.activate();
107                 break;
108             case SDL_WINDOWEVENT_LEAVE:        
109                 widget.release();
110                 break;
111             case SDL_WINDOWEVENT_FOCUS_GAINED: break;
112             case SDL_WINDOWEVENT_FOCUS_LOST:   
113                 widget.release();
114                 break;
115             case SDL_WINDOWEVENT_CLOSE:        
116                 widget.release();
117                 break;
118             default: break;
119         }
120     }
121 
122     void keyboard_eh( in SDL_KeyboardEvent ev ) 
123     { 
124         makeCurrent();
125         DiKeyboardEvent oev;
126         oev.pressed = (ev.state == SDL_PRESSED);
127         oev.scan = ev.keysym.scancode;
128         oev.key = ev.keysym.sym;
129         oev.repeat = cast(bool)ev.repeat;
130         
131         oev.mod = 
132                   ( ev.keysym.mod & KMOD_LSHIFT ? DiKeyboardEvent.Mod.LSHIFT : 0 ) |
133                   ( ev.keysym.mod & KMOD_RSHIFT ? DiKeyboardEvent.Mod.RSHIFT : 0 ) |
134                   ( ev.keysym.mod & KMOD_LCTRL  ? DiKeyboardEvent.Mod.LCTRL  : 0 ) |
135                   ( ev.keysym.mod & KMOD_RCTRL  ? DiKeyboardEvent.Mod.RCTRL  : 0 ) | 
136                   ( ev.keysym.mod & KMOD_LALT   ? DiKeyboardEvent.Mod.LALT   : 0 ) |
137                   ( ev.keysym.mod & KMOD_RALT   ? DiKeyboardEvent.Mod.RALT   : 0 ) |
138                   ( ev.keysym.mod & KMOD_LGUI   ? DiKeyboardEvent.Mod.LGUI   : 0 ) |
139                   ( ev.keysym.mod & KMOD_RGUI   ? DiKeyboardEvent.Mod.RGUI   : 0 ) |
140                   ( ev.keysym.mod & KMOD_NUM    ? DiKeyboardEvent.Mod.NUM    : 0 ) |
141                   ( ev.keysym.mod & KMOD_CAPS   ? DiKeyboardEvent.Mod.CAPS   : 0 ) |
142                   ( ev.keysym.mod & KMOD_MODE   ? DiKeyboardEvent.Mod.MODE   : 0 ) |
143                   ( ev.keysym.mod & KMOD_CTRL   ? DiKeyboardEvent.Mod.CTRL   : 0 ) |
144                   ( ev.keysym.mod & KMOD_SHIFT  ? DiKeyboardEvent.Mod.SHIFT  : 0 ) |
145                   ( ev.keysym.mod & KMOD_ALT    ? DiKeyboardEvent.Mod.ALT    : 0 ) |
146                   ( ev.keysym.mod & KMOD_GUI    ? DiKeyboardEvent.Mod.GUI    : 0 );
147 
148         widget.keyboard( mpos, oev );
149     }
150 
151     void textinput_eh( in SDL_TextInputEvent ev )
152     {
153         import std.utf : toUTF32;
154         auto str = toUTF32( ev.text[0 .. 4].dup );
155         widget.evtext( mpos, DiTextEvent( str[0] ) );
156     }
157 
158     void mouse_button_eh( in SDL_MouseButtonEvent ev ) 
159     { 
160         makeCurrent();
161         mpos.x = ev.x;
162         mpos.y = ev.y;
163 
164         auto me = DiMouseEvent( ev.state == SDL_PRESSED ? DiMouseEvent.Type.PRESSED : 
165                                 DiMouseEvent.Type.RELEASED, 0 );
166         switch( ev.button )
167         {
168             case SDL_BUTTON_LEFT:   me.btn = DiMouseEvent.Button.LEFT; break;
169             case SDL_BUTTON_MIDDLE: me.btn = DiMouseEvent.Button.MIDDLE; break;
170             case SDL_BUTTON_RIGHT:  me.btn = DiMouseEvent.Button.RIGHT; break;
171             case SDL_BUTTON_X1:     me.btn = DiMouseEvent.Button.X1; break;
172             case SDL_BUTTON_X2:     me.btn = DiMouseEvent.Button.X2; break;
173             default: 
174                 throw new DiAppException( "Undefined mouse button: " ~ to!string( ev.button ) );
175         }
176 
177         me.data = mpos;
178         widget.mouse( mpos, me );
179     }
180 
181     void mouse_motion_eh( in SDL_MouseMotionEvent ev ) 
182     { 
183         makeCurrent();
184         mpos.x = ev.x;
185         mpos.y = ev.y;
186 
187         auto me = DiMouseEvent( DiMouseEvent.Type.MOTION, 0 );
188         me.btn = 
189           ( ev.state & SDL_BUTTON_LMASK  ? DiMouseEvent.Button.LEFT : 0 ) |
190           ( ev.state & SDL_BUTTON_MMASK  ? DiMouseEvent.Button.MIDDLE : 0 ) |
191           ( ev.state & SDL_BUTTON_RMASK  ? DiMouseEvent.Button.RIGHT : 0 ) |
192           ( ev.state & SDL_BUTTON_X1MASK ? DiMouseEvent.Button.X1 : 0 ) |
193           ( ev.state & SDL_BUTTON_X2MASK ? DiMouseEvent.Button.X2 : 0 );
194         me.data = mpos;
195         widget.mouse( mpos, me );
196     }
197 
198     void mouse_wheel_eh( in SDL_MouseWheelEvent ev )
199     {
200         makeCurrent();
201         auto me = DiMouseEvent( DiMouseEvent.Type.WHEEL, 0 );
202         me.data = ivec2( ev.x, ev.y );
203         widget.mouse( mpos, me );
204     }
205 
206     void joystick_eh( in DiJoyEvent je ) 
207     { 
208         widget.joystick( mpos, je ); 
209     }
210 
211 public:
212     EmptySignal show;
213     EmptySignal hide;
214 
215     this( string title, string fontname, DiWidget delegate(DiContext) createWidget )
216     {
217         window = SDL_CreateWindow( title.ptr,
218                                    SDL_WINDOWPOS_UNDEFINED,
219                                    SDL_WINDOWPOS_UNDEFINED,
220                                    800, 600,
221                                    SDL_WINDOW_OPENGL | 
222                                    SDL_WINDOW_HIDDEN |
223                                    SDL_WINDOW_RESIZABLE );
224 
225         if( window is null )
226             throw new DiAppException( "Couldn't create SDL window: " ~ toDString( SDL_GetError() ) );
227 
228         context = SDL_GL_CreateContext( window );
229 
230         if( context is null )
231             throw new DiAppException( "Couldn't create SDL context: " ~ toDString( SDL_GetError() ) );
232 
233         DerelictGL3.reload();
234 
235         SDL_GL_SetSwapInterval(1);
236 
237         glEnable( GL_BLEND );
238         glEnable( GL_SCISSOR_TEST );
239 
240         glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
241         glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
242 
243         DiApplication.singleton.windows[window] = this;
244 
245         show.connect({ SDL_ShowWindow( window ); });
246         hide.connect({ SDL_HideWindow( window ); });
247 
248         auto ctx = new DiGLContext( fontname );
249 
250         setClear = &((cast(DiGLDrawStack)(ctx.drawStack)).setClear);
251 
252         widget = createWidget(ctx);
253 
254         if( widget is null )
255             throw new DiAppException( "Couldn't create DiWidget" );
256     }
257 
258     ~this()
259     {
260         clear(widget);
261 
262         if( context !is null ) SDL_GL_DeleteContext( context );
263         if( window !is null ) SDL_DestroyWindow( window );
264     }
265 }
266 
267 
268 class DiApplication
269 {
270 package:
271     static DiApplication singleton;
272 
273     DiAppWindow[SDL_Window*] windows;
274 
275 private:
276     SDL_Joystick *joy_dev = null;
277 
278     this()
279     {
280         DerelictSDL2.load();
281         DerelictGL3.load();
282 
283         if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK ) < 0 )
284             throw new DiAppException( "Couldn't init SDL: " ~ toDString( SDL_GetError() ) );
285 
286         string joy_name = "any";
287 
288         int num_joys = SDL_NumJoysticks();
289         if( num_joys > 0 && joy_name.length )
290         {
291             SDL_JoystickEventState( SDL_ENABLE );
292             int dev_index = 0;
293             if( num_joys != 1 || joy_name != "any" )
294                 while( joy_name != toDString( SDL_JoystickNameForIndex( dev_index ) ) )
295                 {
296                     dev_index++;
297                     if( dev_index > num_joys )
298                     {
299                         dev_index = 0;
300                         break;
301                     }
302                 }
303 
304             joy_dev = SDL_JoystickOpen( dev_index );
305             debug log_info( "enable joy: %s", SDL_JoystickName(joy_dev) );
306         }
307 
308         SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE, 32 );
309         SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,  24 );
310         SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
311     }
312 
313     ~this()
314     {
315         if( SDL_JoystickClose !is null && joy_dev !is null )
316             SDL_JoystickClose( joy_dev );
317 
318         if( SDL_Quit !is null ) SDL_Quit();
319     }
320 
321     DiAppWindow w; // last selected
322 
323     void joystick_eh( uint joy, DiJoyEvent.Type type, size_t no )
324     {
325         DiJoyEvent je;
326 
327         je.joy = joy;
328         je.type = type;
329         je.no = no;
330 
331         foreach( i; 0 .. SDL_JoystickNumAxes( joy_dev ) )
332             je.axis ~= SDL_JoystickGetAxis( joy_dev, i ) / 32768.0; 
333 
334         foreach( i; 0 .. SDL_JoystickNumBalls( joy_dev ) )
335         {
336             ivec2 d;
337             if( SDL_JoystickGetBall( joy_dev, i, d.data.ptr, d.data.ptr+1 ) )
338                 je.balls ~= d;
339         }
340 
341         foreach( i; 0 .. SDL_JoystickNumButtons( joy_dev ) )
342             je.buttons ~= cast(bool)SDL_JoystickGetButton( joy_dev, i );
343 
344         foreach( i; 0 .. SDL_JoystickNumHats( joy_dev ) )
345             je.hats ~= SDL_JoystickGetHat( joy_dev, i );
346 
347         if( w ) w.joystick_eh( je );
348     }
349 
350     bool work()
351     {
352         SDL_Event event;
353 
354         DiAppWindow selectWindow( uint id )
355         {
356             auto wid = SDL_GetWindowFromID( event.window.windowID ) in windows;
357             if( wid ) return *wid;
358             else return null;
359         }
360 
361         while( SDL_PollEvent(&event) )
362         {
363             switch( event.type )
364             {
365                 case SDL_QUIT: return false;
366 
367                 case SDL_WINDOWEVENT:
368                     w = selectWindow( event.window.windowID );
369                     if(w) w.window_eh( event.window );
370                     break;
371                 case SDL_KEYDOWN:
372                 case SDL_KEYUP: 
373                     w = selectWindow( event.key.windowID );
374                     if(w) w.keyboard_eh( event.key );
375                     break;
376                 case SDL_TEXTEDITING: break;
377                 case SDL_TEXTINPUT: 
378                     w = selectWindow( event.text.windowID );
379                     if(w) w.textinput_eh( event.text );
380                     break;
381 
382                 case SDL_MOUSEMOTION: 
383                     w = selectWindow( event.motion.windowID );
384                     if(w) w.mouse_motion_eh( event.motion );
385                     break;
386                 case SDL_MOUSEBUTTONDOWN:
387                 case SDL_MOUSEBUTTONUP: 
388                     w = selectWindow( event.button.windowID );
389                     if(w) w.mouse_button_eh( event.button );
390                     break;
391                 case SDL_MOUSEWHEEL: 
392                     w = selectWindow( event.wheel.windowID );
393                     if(w) w.mouse_wheel_eh( event.wheel );
394                     break;
395 
396                 case SDL_JOYAXISMOTION:
397                     joystick_eh( event.jaxis.which, DiJoyEvent.Type.AXIS, event.jaxis.axis );
398                     break;
399                 case SDL_JOYBALLMOTION: 
400                     joystick_eh( event.jball.which, DiJoyEvent.Type.BALL, event.jball.ball );
401                     break;
402                 case SDL_JOYHATMOTION:
403                     joystick_eh( event.jhat.which, DiJoyEvent.Type.HAT, event.jhat.hat );
404                     break;
405                 case SDL_JOYBUTTONDOWN:
406                 case SDL_JOYBUTTONUP: 
407                     joystick_eh( event.jbutton.which, DiJoyEvent.Type.BUTTON, event.jbutton.button );
408                     break;
409 
410                 case SDL_JOYDEVICEADDED: break;
411                 case SDL_JOYDEVICEREMOVED: break;
412 
413                 case SDL_CONTROLLERAXISMOTION: break;
414                 case SDL_CONTROLLERBUTTONDOWN: break;
415                 case SDL_CONTROLLERBUTTONUP: break;
416                 case SDL_CONTROLLERDEVICEADDED: break;
417                 case SDL_CONTROLLERDEVICEREMOVED: break;
418                 case SDL_CONTROLLERDEVICEREMAPPED: break;
419 
420                 case SDL_FINGERDOWN: break;
421                 case SDL_FINGERUP: break;
422                 case SDL_FINGERMOTION: break;
423 
424                 case SDL_DOLLARGESTURE: break;
425                 case SDL_DOLLARRECORD: break;
426                 case SDL_MULTIGESTURE: break;
427 
428                 case SDL_CLIPBOARDUPDATE: break;
429 
430                 case SDL_DROPFILE: break;
431                 default: break;
432             }
433         }
434 
435         foreach( id, win; windows ) win.process();
436 
437         return true;
438     }
439 
440 public:
441     static void init()
442     {
443         destroy();
444         singleton = new DiApplication;
445     }
446 
447     static void run() 
448     { 
449         if( singleton is null ) init();
450         while( singleton.work() ) SDL_Delay(1); 
451     }
452 
453     static bool evProc()
454     {
455         if( singleton is null ) init();
456         return singleton.work();
457     }
458 
459     static void destroy()
460     {
461         if( singleton !is null ) clear( singleton );
462     }
463 }