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 }