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 187 void makeCurrent() { SDL_GL_MakeCurrent( win, app.context ); } 188 189 protected: 190 191 /// 192 uint SDLFlags() const @property { return SDL_GetWindowFlags( cast(SDL_Window*)win ); } 193 194 void prepareBaseEventProcessors() 195 { 196 mouse = registerEvProc( new MouseEventProcessor ); 197 event = registerEvProc( new WindowEventProcessor ); 198 key = registerEvProc( new KeyboardEventProcessor ); 199 200 logger.Debug( "mouse: %s, event: %s, key: %s", mouse !is null, event !is null, key !is null ); 201 } 202 203 void prepareSDLWindow( string title, ivec2 sz, bool fullscreen, int display ) 204 { 205 _size = sz; 206 if( display != -1 && display > SDL_GetNumVideoDisplays() - 1 ) 207 throw new DesAppException( format( "No such display: display%d", display ) ); 208 209 auto flags = Flag.OPENGL | Flag.SHOWN | ( fullscreen ? Flag.FULLSCREEN : Flag.RESIZABLE ); 210 211 auto pos = ivec2( SDL_WINDOWPOS_CENTERED ); 212 213 if( display != -1 ) 214 pos = ivec2( SDL_WINDOWPOS_CENTERED_DISPLAY( display ) ); 215 216 win = SDL_CreateWindow( title.toStringz, pos.x, pos.y, _size.x, _size.y, flags ); 217 if( win is null ) 218 throw new DesAppException( "Couldn't create SDL widnow: " ~ toDString( SDL_GetError() ) ); 219 } 220 221 void prepareDrawSignal() 222 { 223 draw.begin.connect( newSlot( 224 { 225 glViewport( 0, 0, _size.x, _size.y ); 226 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 227 })); 228 draw.end.connect( newSlot( { SDL_GL_SwapWindow( win ); } ) ); 229 } 230 231 override void selfDestroy() 232 { 233 if( win !is null ) 234 SDL_DestroyWindow( win ); 235 win = null; 236 debug logger.Debug("pass"); 237 } 238 } 239 240 /++ windows handler 241 242 +/ 243 class DesApp : ExternalMemoryManager 244 { 245 mixin EMM; 246 247 protected: 248 249 SDL_GLContext context = null; 250 DesWindow[uint] windows; 251 DesWindow current; 252 bool is_running; 253 254 public: 255 256 /++ create app 257 258 load `DerelictSDL2`, `DerelictGL3`, 259 init SDL with video mode, set GL attributes, 260 +/ 261 this() 262 { 263 DerelictSDL2.load(); 264 DerelictGL3.load(); 265 266 if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) 267 throw new DesAppException( "Error initializing SDL: " ~ toDString( SDL_GetError() ) ); 268 269 SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE, 32 ); 270 SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 ); 271 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); 272 273 is_running = true; 274 } 275 276 /// single processing step 277 bool step() 278 { 279 if( context is null ) 280 { 281 logger.warn( "no windows" ); 282 return false; 283 } 284 285 if( !procEvents() ) 286 return false; 287 288 foreach( win; windows ) 289 { 290 win.makeCurrent(); 291 win.idle(); 292 win.draw(); 293 } 294 295 return true; 296 } 297 298 /// 299 bool isRunning() @property { return is_running; } 300 301 /++ create and return window from create function `winFunc` 302 303 created window registered as child EMM, create GL contex if it null, 304 calls `prepare` for new window, add window to windows list 305 +/ 306 DesWindow addWindow( DesWindow delegate() winFunc ) 307 { 308 auto win = registerChildEMM( winFunc() ); 309 if( context is null ) 310 { 311 context = SDL_GL_CreateContext( win.win ); 312 313 if( context is null ) 314 throw new DesAppException( "Couldn't create GL context: " ~ toDString( SDL_GetError() ) ); 315 DerelictGL3.reload(); 316 } 317 318 win.setApp( this ); 319 win.prepare(); 320 321 windows[win.id] = win; 322 323 return win; 324 } 325 326 /// 327 void quit() { is_running = false; } 328 329 void startTextInput() { SDL_StartTextInput(); } 330 void stopTextInput() { SDL_StopTextInput(); } 331 332 protected: 333 334 void delay() 335 { 336 import core.thread; 337 Thread.sleep(dur!"usecs"(1)); 338 } 339 340 /++ process all events with SDL_PollEvent 341 342 set current window by windowID in event structure, 343 call process event by current window 344 +/ 345 bool procEvents() 346 { 347 SDL_Event ev; 348 while( SDL_PollEvent( &ev ) ) 349 { 350 switch( ev.type ) 351 { 352 case SDL_QUIT: return false; 353 // set current window 354 case SDL_WINDOWEVENT: setCurrent( ev.window.windowID ); break; 355 case SDL_KEYDOWN: 356 case SDL_KEYUP: setCurrent( ev.key.windowID ); break; 357 case SDL_TEXTEDITING: 358 case SDL_TEXTINPUT: setCurrent( ev.text.windowID ); break; 359 case SDL_MOUSEMOTION: setCurrent( ev.text.windowID ); break; 360 case SDL_MOUSEBUTTONDOWN: 361 case SDL_MOUSEBUTTONUP: setCurrent( ev.button.windowID ); break; 362 case SDL_MOUSEWHEEL: setCurrent( ev.wheel.windowID ); break; 363 default: break; 364 } 365 366 if( current !is null ) 367 current.procEvent( ev ); 368 } 369 return true; 370 } 371 372 /// 373 void setCurrent( uint winID ) { current = windows.get( winID, null ); } 374 375 void selfDestroy() 376 { 377 destroyContext(); 378 shutdown(); 379 } 380 381 void destroyContext() 382 { 383 if( context !is null ) 384 SDL_GL_DeleteContext( context ); 385 context = null; 386 debug logger.Debug("pass"); 387 } 388 389 void shutdown() { if( SDL_Quit !is null ) SDL_Quit(); } 390 }