1 module des.app.base; 2 3 import std.stdio; 4 import std..string; 5 6 public import derelict.opengl3.gl3; 7 public import derelict.sdl2.sdl; 8 9 public import derelict.freetype.ft; 10 11 public import des.util.arch; 12 public import des.util.stdext..string; 13 14 import des.util.logsys; 15 16 import des.app.event; 17 18 import des.app.evproc; 19 20 /// 21 class DesAppException : Exception 22 { 23 /// 24 this( string msg, string file = __FILE__, size_t line = __LINE__ ) @safe pure nothrow 25 { super( msg, file, line ); } 26 } 27 28 /// SDL window with open gl 29 class DesWindow : DesObject 30 { 31 mixin DES; 32 33 protected: 34 35 /// 36 abstract void prepare(); 37 38 SDL_Window* win = null; 39 ivec2 _size; 40 41 DesApp app; 42 43 SDLEventProcessor[] processors; 44 45 public: 46 47 enum Flag : uint 48 { 49 FULLSCREEN = SDL_WINDOW_FULLSCREEN, /// `SDL_WINDOW_FULLSCREEN` 50 FULLSCREEN_DESKTOP = SDL_WINDOW_FULLSCREEN_DESKTOP, /// `SDL_WINDOW_FULLSCREEN_DESKTOP` 51 OPENGL = SDL_WINDOW_OPENGL, /// `SDL_WINDOW_OPENGL` 52 SHOWN = SDL_WINDOW_SHOWN, /// `SDL_WINDOW_SHOWN` 53 HIDDEN = SDL_WINDOW_HIDDEN, /// `SDL_WINDOW_HIDDEN` 54 BORDERLESS = SDL_WINDOW_BORDERLESS, /// `SDL_WINDOW_BORDERLESS` 55 RESIZABLE = SDL_WINDOW_RESIZABLE, /// `SDL_WINDOW_RESIZABLE` 56 MINIMIZED = SDL_WINDOW_MINIMIZED, /// `SDL_WINDOW_MINIMIZED` 57 MAXIMIZED = SDL_WINDOW_MAXIMIZED, /// `SDL_WINDOW_MAXIMIZED` 58 INPUT_GRABBED = SDL_WINDOW_INPUT_GRABBED, /// `SDL_WINDOW_INPUT_GRABBED` 59 INPUT_FOCUS = SDL_WINDOW_INPUT_FOCUS, /// `SDL_WINDOW_INPUT_FOCUS` 60 MOUSE_FOCUS = SDL_WINDOW_MOUSE_FOCUS, /// `SDL_WINDOW_MOUSE_FOCUS` 61 FOREIGN = SDL_WINDOW_FOREIGN, /// `SDL_WINDOW_FOREIGN` 62 ALLOW_HIGHDPI = SDL_WINDOW_ALLOW_HIGHDPI, /// `SDL_WINDOW_ALLOW_HIGHDPI` 63 } 64 65 /// 66 SignalBox!() draw; 67 /// 68 Signal!() idle; 69 70 /// 71 MouseEventProcessor mouse; 72 /// 73 WindowEventProcessor event; 74 /// 75 KeyboardEventProcessor key; 76 77 this( string title, ivec2 sz, bool fullscreen = false, int display = -1 ) 78 { 79 prepareBaseEventProcessors(); 80 prepareSDLWindow( title, sz, fullscreen, display ); 81 prepareDrawSignal(); 82 logger.Debug( "pass" ); 83 } 84 85 /// 86 //Предполагается, что входящее событие предназначено именно этому окну 87 void procEvent( in SDL_Event ev ) 88 { 89 foreach( p; processors ) if( p.procSDLEvent(ev) ) break; 90 91 if( ev.type == SDL_WINDOWEVENT && 92 ev.window.event == SDL_WINDOWEVENT_RESIZED ) 93 _size = ivec2( ev.window.data1, ev.window.data2 ); 94 } 95 96 /++ register additional event processor 97 + Retruns: 98 + registered `T` object 99 +/ 100 auto registerEvProc(T)( T ep ) 101 if( is( T : SDLEventProcessor ) ) 102 { 103 foreach( ex; processors ) if( ex == ep ) return ep; 104 processors ~= registerChildEMM( ep ); 105 return ep; 106 } 107 108 /++ create and register additional event processor 109 + Params: 110 + args = pass to `T` ctor 111 + Returns: 112 + created `T` object 113 +/ 114 auto newEvProc(T,Args...)( Args args ) 115 if( is( T : SDLEventProcessor ) ) 116 { return registerEvProc( new T(args) ); } 117 118 @property 119 { 120 /// 121 uint id() { return SDL_GetWindowID( win ); } 122 /// 123 ivec2 size() const { return _size; } 124 125 /// 126 float brightness() const 127 { return SDL_GetWindowBrightness( cast(SDL_Window*)win ); } 128 129 /// 130 float brightness( float v ) 131 { 132 SDL_SetWindowBrightness( win, v ); 133 return v; 134 } 135 136 /// 137 int displayIndex() const 138 { return SDL_GetWindowDisplayIndex( cast(SDL_Window*)win ); } 139 } 140 141 /// 142 void show() { SDL_ShowWindow( win ); } 143 /// 144 void hide() { SDL_HideWindow( win ); } 145 146 /// 147 bool checkFlag( Flag flag ) const { return cast(bool)( SDLFlags & flag ); } 148 149 const @property 150 { 151 /// 152 bool isFullscreen() { return checkFlag( Flag.FULLSCREEN ); } 153 /// 154 bool isFullscreenDesktop() { return checkFlag( Flag.FULLSCREEN_DESKTOP ); } 155 /// 156 bool isOpenGL() { return checkFlag( Flag.OPENGL ); } 157 /// 158 bool isShown() { return checkFlag( Flag.SHOWN ); } 159 /// 160 bool isHidden() { return checkFlag( Flag.HIDDEN ); } 161 /// 162 bool isBorderless() { return checkFlag( Flag.BORDERLESS ); } 163 /// 164 bool isResizable() { return checkFlag( Flag.RESIZABLE ); } 165 /// 166 bool isMinimized() { return checkFlag( Flag.MINIMIZED ); } 167 /// 168 bool isMaximized() { return checkFlag( Flag.MAXIMIZED ); } 169 /// 170 bool isInputGrabbed() { return checkFlag( Flag.INPUT_GRABBED ); } 171 /// 172 bool isInputFocus() { return checkFlag( Flag.INPUT_FOCUS ); } 173 /// 174 bool isMouseFocus() { return checkFlag( Flag.MOUSE_FOCUS ); } 175 /// 176 bool isForeign() { return checkFlag( Flag.FOREIGN ); } 177 /// 178 bool isAllowHighdpi() { return checkFlag( Flag.ALLOW_HIGHDPI ); } 179 } 180 181 void startTextInput() { app.startTextInput(); } 182 void stopTextInput() { app.stopTextInput(); } 183 184 package: 185 void setApp( DesApp owner ) { app = owner; } 186 void makeCurrent() { SDL_GL_MakeCurrent( win, app.context );} 187 188 protected: 189 190 /// 191 uint SDLFlags() const @property { return SDL_GetWindowFlags( cast(SDL_Window*)win ); } 192 193 void prepareBaseEventProcessors() 194 { 195 mouse = registerEvProc( new MouseEventProcessor ); 196 event = registerEvProc( new WindowEventProcessor ); 197 key = registerEvProc( new KeyboardEventProcessor ); 198 199 logger.Debug( "mouse: %s, event: %s, key: %s", mouse !is null, event !is null, key !is null ); 200 } 201 202 void prepareSDLWindow( string title, ivec2 sz, bool fullscreen, int display ) 203 { 204 _size = sz; 205 if( display != -1 && display > SDL_GetNumVideoDisplays() - 1 ) 206 throw new DesAppException( format( "No such display: display%d", display ) ); 207 208 auto flags = Flag.OPENGL | Flag.SHOWN | ( fullscreen ? Flag.FULLSCREEN : Flag.RESIZABLE ); 209 210 auto pos = ivec2( SDL_WINDOWPOS_CENTERED ); 211 212 if( display != -1 ) 213 pos = ivec2( SDL_WINDOWPOS_CENTERED_DISPLAY( display ) ); 214 215 win = SDL_CreateWindow( title.toStringz, pos.x, pos.y, _size.x, _size.y, flags ); 216 if( win is null ) 217 throw new DesAppException( "Couldn't create SDL widnow: " ~ toDString( SDL_GetError() ) ); 218 } 219 220 void prepareDrawSignal() 221 { 222 draw.begin.connect( newSlot( 223 { 224 glViewport( 0, 0, _size.x, _size.y ); 225 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 226 })); 227 draw.end.connect( newSlot( { SDL_GL_SwapWindow( win ); } ) ); 228 } 229 230 override void selfDestroy() 231 { 232 if( win !is null ) 233 SDL_DestroyWindow( win ); 234 win = null; 235 debug logger.Debug("pass"); 236 } 237 } 238 239 /++ windows handler 240 241 +/ 242 class DesApp : ExternalMemoryManager 243 { 244 mixin EMM; 245 246 protected: 247 248 SDL_GLContext context = null; 249 DesWindow[uint] windows; 250 DesWindow current; 251 bool is_running; 252 253 public: 254 255 /++ create app 256 257 load `DerelictSDL2`, `DerelictGL3`, 258 init SDL with video mode, set GL attributes, 259 +/ 260 this() 261 { 262 DerelictSDL2.load(); 263 DerelictGL3.load(); 264 265 if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) 266 throw new DesAppException( "Error initializing SDL: " ~ toDString( SDL_GetError() ) ); 267 268 SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE, 32 ); 269 SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 ); 270 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); 271 272 is_running = true; 273 } 274 275 /// single processing step 276 bool step() 277 { 278 if( context is null ) 279 { 280 logger.warn( "no windows" ); 281 return false; 282 } 283 284 if( !procEvents() ) 285 return false; 286 287 foreach( win; windows ) 288 { 289 win.makeCurrent(); 290 win.idle(); 291 win.draw(); 292 } 293 294 return true; 295 } 296 297 /// 298 bool isRunning() @property { return is_running; } 299 300 /++ create and return window from create function `winFunc` 301 302 created window registered as child EMM, create GL contex if it null, 303 calls `prepare` for new window, add window to windows list 304 +/ 305 DesWindow addWindow( DesWindow delegate() winFunc ) 306 { 307 auto win = registerChildEMM( winFunc() ); 308 if( context is null ) 309 { 310 context = SDL_GL_CreateContext( win.win ); 311 312 if( context is null ) 313 throw new DesAppException( "Couldn't create GL context: " ~ toDString( SDL_GetError() ) ); 314 DerelictGL3.reload(); 315 } 316 317 win.setApp( this ); 318 win.prepare(); 319 320 windows[win.id] = win; 321 322 return win; 323 } 324 325 /// 326 void quit() { is_running = false; } 327 328 void startTextInput() { SDL_StartTextInput(); } 329 void stopTextInput() { SDL_StopTextInput(); } 330 331 protected: 332 333 void delay() 334 { 335 import core.thread; 336 Thread.sleep(dur!"usecs"(1)); 337 } 338 339 /++ process all events with SDL_PollEvent 340 341 set current window by windowID in event structure, 342 call process event by current window 343 +/ 344 bool procEvents() 345 { 346 SDL_Event ev; 347 while( SDL_PollEvent( &ev ) ) 348 { 349 switch( ev.type ) 350 { 351 case SDL_QUIT: return false; 352 // set current window 353 case SDL_WINDOWEVENT: setCurrent( ev.window.windowID ); break; 354 case SDL_KEYDOWN: 355 case SDL_KEYUP: setCurrent( ev.key.windowID ); break; 356 case SDL_TEXTEDITING: 357 case SDL_TEXTINPUT: setCurrent( ev.text.windowID ); break; 358 case SDL_MOUSEMOTION: setCurrent( ev.text.windowID ); break; 359 case SDL_MOUSEBUTTONDOWN: 360 case SDL_MOUSEBUTTONUP: setCurrent( ev.button.windowID ); break; 361 case SDL_MOUSEWHEEL: setCurrent( ev.wheel.windowID ); break; 362 default: break; 363 } 364 365 if( current !is null ) 366 current.procEvent( ev ); 367 } 368 return true; 369 } 370 371 /// 372 void setCurrent( uint winID ) { current = windows.get( winID, null ); } 373 374 void selfDestroy() 375 { 376 destroyContext(); 377 shutdown(); 378 } 379 380 void destroyContext() 381 { 382 if( context !is null ) 383 SDL_GL_DeleteContext( context ); 384 context = null; 385 debug logger.Debug("pass"); 386 } 387 388 void shutdown() { if( SDL_Quit !is null ) SDL_Quit(); } 389 }