In glutMainLoop, the checking of ExecState, and wether it's equal to
[freeglut] / src / fg_main.c
index f6a94a6..0f47c8d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * freeglut_main.c
+ * fg_main.c
  *
  * The windows message processing methods.
  *
 
 #include <GL/freeglut.h>
 #include "fg_internal.h"
-#ifdef HAVE_ERRNO_H
-#    include <errno.h>
-#endif
+#include <errno.h>
 #include <stdarg.h>
-#ifdef  HAVE_VFPRINTF
-#    define VFPRINTF(s,f,a) vfprintf((s),(f),(a))
-#elif defined(HAVE__DOPRNT)
-#    define VFPRINTF(s,f,a) _doprnt((f),(a),(s))
-#else
-#    define VFPRINTF(s,f,a)
-#endif
 
 /*
  * Try to get the maximum value allowed for ints, falling back to the minimum
 #    define MIN(a,b) (((a)<(b)) ? (a) : (b))
 #endif
 
-extern void fgPlatformReshapeWindow ( SFG_Window *window, int width, int height );
-extern void fgPlatformDisplayWindow ( SFG_Window *window );
+extern void fgProcessWork   ( SFG_Window *window );
 extern fg_time_t fgPlatformSystemTime ( void );
 extern void fgPlatformSleepForEvents( fg_time_t msec );
 extern void fgPlatformProcessSingleEvent ( void );
 extern void fgPlatformMainLoopPreliminaryWork ( void );
 
-
+extern void fgPlatformInitWork(SFG_Window* window);
+extern void fgPlatformPosResZordWork(SFG_Window* window, unsigned int workMask);
+extern void fgPlatformVisibilityWork(SFG_Window* window);
 
 
 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
 
-static void fghReshapeWindow ( SFG_Window *window, int width, int height )
+void fghOnReshapeNotify(SFG_Window *window, int width, int height, GLboolean forceNotify)
 {
-    SFG_Window *current_window = fgStructure.CurrentWindow;
+    GLboolean notify = GL_FALSE;
 
-    freeglut_return_if_fail( window != NULL );
+    if( width  != window->State.Width ||
+        height != window->State.Height )
+    {
+        window->State.Width = width;
+        window->State.Height = height;
 
-       fgPlatformReshapeWindow ( window, width, height );
+        notify = GL_TRUE;
+    }
 
-    if( FETCH_WCB( *window, Reshape ) )
-        INVOKE_WCB( *window, Reshape, ( width, height ) );
-    else
+    if (notify || forceNotify)
     {
-        fgSetWindow( window );
-        glViewport( 0, 0, width, height );
+        SFG_Window *saved_window = fgStructure.CurrentWindow;
+
+        INVOKE_WCB( *window, Reshape, ( width, height ) );
+
+        /*
+         * Force a window redraw.  In Windows at least this is only a partial
+         * solution:  if the window is increasing in size in either dimension,
+         * the already-drawn part does not get drawn again and things look funny.
+         * But without this we get this bad behaviour whenever we resize the
+         * window.
+         * DN: Hmm.. the above sounds like a concern only in single buffered mode...
+         */
+        window->State.WorkMask |= GLUT_DISPLAY_WORK;
+        if( window->IsMenu )
+            fgSetWindow( saved_window );
     }
+}
 
-    /*
-     * Force a window redraw.  In Windows at least this is only a partial
-     * solution:  if the window is increasing in size in either dimension,
-     * the already-drawn part does not get drawn again and things look funny.
-     * But without this we get this bad behaviour whenever we resize the
-     * window.
-     * DN: Hmm.. the above sounds like a concern only in single buffered mode...
-     */
-    window->State.Redisplay = GL_TRUE;
+void fghOnPositionNotify(SFG_Window *window, int x, int y, GLboolean forceNotify)
+{
+    GLboolean notify = GL_FALSE;
 
-    if( window->IsMenu )
-        fgSetWindow( current_window );
+    if( x  != window->State.Xpos ||
+        y != window->State.Ypos )
+    {
+        window->State.Xpos = x;
+        window->State.Ypos = y;
+
+        notify = GL_TRUE;
+    }
+
+    if (notify || forceNotify)
+    {
+        SFG_Window *saved_window = fgStructure.CurrentWindow;
+        INVOKE_WCB( *window, Position, ( x, y ) );
+        fgSetWindow( saved_window );
+    }
 }
 
 /*
  * Calls a window's redraw method. This is used when
- * a redraw is forced by the incoming window messages.
+ * a redraw is forced by the incoming window messages,
+ * or if a redisplay is otherwise pending.
+ * this is lean and mean without checks as it is
+ * currently only called from fghcbDisplayWindow which
+ * only calls this if the window is visible and needs
+ * a redisplay.
+ * Note that the fgSetWindow call on Windows makes the
+ * right device context current on windows, allowing
+ * direct drawing without BeginPaint/EndPaint in the
+ * WM_PAINT handler.
  */
 void fghRedrawWindow ( SFG_Window *window )
 {
     SFG_Window *current_window = fgStructure.CurrentWindow;
 
-    freeglut_return_if_fail( window );
-
-    if( window->State.NeedToInitContext ) {
-        INVOKE_WCB( *window, InitContext, ());
-        window->State.NeedToInitContext = GL_FALSE;
-    }
-
-    freeglut_return_if_fail( FETCH_WCB ( *window, Display ) );
+    fgSetWindow( window );
+    INVOKE_WCB( *window, Display, ( ) );
 
-    window->State.Redisplay = GL_FALSE;
+    fgSetWindow( current_window );
+}
 
-    freeglut_return_if_fail( window->State.Visible );
+void fghRedrawWindowAndChildren ( SFG_Window *window )
+{
+    SFG_Window* child;
 
-    fgSetWindow( window );
+    fghRedrawWindow(window);
 
-    if( window->State.NeedToResize )
+    for( child = ( SFG_Window * )window->Children.First;
+         child;
+         child = ( SFG_Window * )child->Node.Next )
     {
-        /* Set need to resize to false before calling fghReshapeWindow, otherwise
-           in the case the user's reshape callback calls glutReshapeWindow,
-           his request would get canceled after fghReshapeWindow gets called.
-         */
-        window->State.NeedToResize = GL_FALSE;
-
-        fghReshapeWindow(
-            window,
-            window->State.Width,
-            window->State.Height
-        );
+        fghRedrawWindowAndChildren(child);
     }
-
-    INVOKE_WCB( *window, Display, ( ) );
-
-    fgSetWindow( current_window );
 }
 
 
-static void fghcbDisplayWindow( SFG_Window *window,
-                                SFG_Enumerator *enumerator )
+static void fghcbProcessWork( SFG_Window *window,
+                              SFG_Enumerator *enumerator )
 {
-    if( window->State.Redisplay &&
-        window->State.Visible )
-    {
-        window->State.Redisplay = GL_FALSE;
-               fgPlatformDisplayWindow ( window );
-    }
+    if( window->State.WorkMask )
+        fgProcessWork ( window );
 
-    fgEnumSubWindows( window, fghcbDisplayWindow, enumerator );
+    fgEnumSubWindows( window, fghcbProcessWork, enumerator );
 }
 
 /*
- * Make all windows perform a display call
+ * Make all windows process their work list
  */
-static void fghDisplayAll( void )
+static void fghProcessWork( void )
 {
     SFG_Enumerator enumerator;
 
     enumerator.found = GL_FALSE;
     enumerator.data  =  NULL;
 
-    fgEnumWindows( fghcbDisplayWindow, &enumerator );
+    fgEnumWindows( fghcbProcessWork, &enumerator );
 }
 
 /*
@@ -221,7 +227,7 @@ static void fghCheckTimers( void )
         SFG_Timer *timer = fgState.Timers.First;
 
         if( timer->TriggerTime > checkTime )
-            /* XXX: are timers always sorted by triggerTime? If not, this and fghNextTimer are wrong */
+            /* Timers are sorted by triggerTime */
             break;
 
         fgListRemove( &fgState.Timers, &timer->Node );
@@ -268,13 +274,13 @@ void fgError( const char *fmt, ... )
         va_end( ap );
 
     } else {
-#if FREEGLUT_ERRORS
+#ifdef FREEGLUT_PRINT_ERRORS
         va_start( ap, fmt );
 
         fprintf( stderr, "freeglut ");
         if( fgState.ProgramName )
             fprintf( stderr, "(%s): ", fgState.ProgramName );
-        VFPRINTF( stderr, fmt, ap );
+        vfprintf( stderr, fmt, ap );
         fprintf( stderr, "\n" );
 
         va_end( ap );
@@ -301,13 +307,13 @@ void fgWarning( const char *fmt, ... )
         va_end( ap );
 
     } else {
-#if FREEGLUT_WARNINGS
+#ifdef FREEGLUT_PRINT_WARNINGS
         va_start( ap, fmt );
 
         fprintf( stderr, "freeglut ");
         if( fgState.ProgramName )
             fprintf( stderr, "(%s): ", fgState.ProgramName );
-        VFPRINTF( stderr, fmt, ap );
+        vfprintf( stderr, fmt, ap );
         fprintf( stderr, "\n" );
 
         va_end( ap );
@@ -317,29 +323,28 @@ void fgWarning( const char *fmt, ... )
 
 
 /*
- * Indicates whether a redisplay is pending for ANY window.
+ * Indicates whether work is pending for ANY window.
  *
  * The current mechanism is to walk all of the windows and ask if
- * a redisplay is pending. We have a short-circuit early
- * return if we find any.
+ * work is pending. We have a short-circuit early return if we find any.
  */
-static void fghHavePendingRedisplaysCallback( SFG_Window* w, SFG_Enumerator* e)
+static void fghHavePendingWorkCallback( SFG_Window* w, SFG_Enumerator* e)
 {
-    if( w->State.Redisplay && w->State.Visible )
+    if( w->State.WorkMask )
     {
         e->found = GL_TRUE;
         e->data = w;
         return;
     }
-    fgEnumSubWindows( w, fghHavePendingRedisplaysCallback, e );
+    fgEnumSubWindows( w, fghHavePendingWorkCallback, e );
 }
-static int fghHavePendingRedisplays (void)
+static int fghHavePendingWork (void)
 {
     SFG_Enumerator enumerator;
 
     enumerator.found = GL_FALSE;
     enumerator.data = NULL;
-    fgEnumWindows( fghHavePendingRedisplaysCallback, &enumerator );
+    fgEnumWindows( fghHavePendingWorkCallback, &enumerator );
     return !!enumerator.data;
 }
 
@@ -348,12 +353,13 @@ static int fghHavePendingRedisplays (void)
  */
 static fg_time_t fghNextTimer( void )
 {
-    fg_time_t currentTime = fgElapsedTime();
-    SFG_Timer *timer = fgState.Timers.First;
+    fg_time_t currentTime;
+    SFG_Timer *timer = fgState.Timers.First;    /* timers are sorted by trigger time, so only have to check the first */
 
     if( !timer )
         return INT_MAX;
 
+    currentTime = fgElapsedTime();
     if( timer->TriggerTime < currentTime )
         return 0;
     else
@@ -364,16 +370,71 @@ static void fghSleepForEvents( void )
 {
     fg_time_t msec;
 
-    if( fghHavePendingRedisplays( ) )
+    if( fghHavePendingWork( ) )
         return;
 
     msec = fghNextTimer( );
     /* XXX Should use GLUT timers for joysticks... */
     /* XXX Dumb; forces granularity to .01sec */
-    if( fgState.NumActiveJoysticks>0 && ( msec > 10 ) )     
+    if( fgState.NumActiveJoysticks>0 && ( msec > 10 ) )
         msec = 10;
 
-       fgPlatformSleepForEvents ( msec );
+    fgPlatformSleepForEvents ( msec );
+}
+
+
+/* Step through the work list */
+void fgProcessWork(SFG_Window *window)
+{
+    unsigned int workMask = window->State.WorkMask;
+    /* Now clear it so that any callback generated by the actions below can set work again */
+    window->State.WorkMask = 0;
+
+    if (workMask&~GLUT_DISPLAY_WORK)    /* Display work is the common case, skip all the below at once */
+    {
+        if (workMask & GLUT_INIT_WORK)
+        {
+            /* This is before the first display callback: if needed for the platform,
+             * call a few callbacks to inform user of window size, position, etc
+             */
+            fgPlatformInitWork(window);
+
+            /* Call init context callback */
+            INVOKE_WCB( *window, InitContext, ());
+
+            /* Lastly, check if we have a display callback, error out if not
+             * This is the right place to do it, as the redisplay will be
+             * next right after we exit this function, so there is no more
+             * opportunity for the user to register a callback for this window.
+             */
+            if (!FETCH_WCB(*window, Display))
+                fgError ( "ERROR:  No display callback registered for window %d\n", window->ID );
+        }
+
+        /* On windows we can position, resize and change z order at the same time */
+        if (workMask & (GLUT_POSITION_WORK|GLUT_SIZE_WORK|GLUT_ZORDER_WORK|GLUT_FULL_SCREEN_WORK))
+        {
+            fgPlatformPosResZordWork(window,workMask);
+        }
+
+        if (workMask & GLUT_VISIBILITY_WORK)
+        {
+            fgPlatformVisibilityWork(window);
+        }
+    }
+
+    /* check window state's workmask as well as some of the above callbacks might have generated redisplay requests. We can deal with those right now instead of wait for the next mainloop iteration. */
+    if (workMask & GLUT_DISPLAY_WORK || window->State.WorkMask & GLUT_DISPLAY_WORK)
+    {
+        if( window->State.Visible )
+        {
+            /* Strip out display work from the work list */
+            /* NB: do this before the display callback is called as user might call postredisplay in his display callback */
+            window->State.WorkMask &= ~GLUT_DISPLAY_WORK;
+
+            fghRedrawWindow ( window );
+        }
+    }
 }
 
 
@@ -384,13 +445,23 @@ static void fghSleepForEvents( void )
  */
 void FGAPIENTRY glutMainLoopEvent( void )
 {
+    /* Process input */
        fgPlatformProcessSingleEvent ();
 
     if( fgState.Timers.First )
         fghCheckTimers( );
     if (fgState.NumActiveJoysticks>0)   /* If zero, don't poll joysticks */
         fghCheckJoystickPolls( );
-    fghDisplayAll( );
+
+    /* Perform work on the window (position, reshape, display, etc) */
+    fghProcessWork( );
+
+    /* Check OpenGL error state if requested.
+     * Don't call if no more open windows (can happen if user closes window from
+     * title bar), would lead to infinite error loop in glutReportErrors
+     */
+    if (fgState.GLDebugSwitch && fgStructure.CurrentWindow)
+        glutReportErrors( );
 
     fgCloseWindows( );
 }
@@ -408,14 +479,16 @@ void FGAPIENTRY glutMainLoop( void )
     if (!fgStructure.Windows.First)
         fgError(" ERROR:  glutMainLoop called with no windows created.");
 
-       fgPlatformMainLoopPreliminaryWork ();
+    fgPlatformMainLoopPreliminaryWork ();
 
     fgState.ExecState = GLUT_EXEC_STATE_RUNNING ;
-    while( fgState.ExecState == GLUT_EXEC_STATE_RUNNING )
+    for(;;)
     {
         SFG_Window *window;
 
         glutMainLoopEvent( );
+        if( fgState.ExecState != GLUT_EXEC_STATE_RUNNING )
+            break;
         /*
          * Step through the list of windows, seeing if there are any
          * that are not menus