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 }