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 }