First shot at SDL_SetWindowDragAreas().
authorRyan C. Gordon <icculus@icculus.org>
Tue, 27 May 2014 01:27:42 -0400
changeset 8931 44d8a2f4b431
parent 8930 d000e6339d41
child 8932 7eacbfcbb313
First shot at SDL_SetWindowDragAreas(). Only Cocoa implemented right now.
.hgignore
include/SDL_video.h
src/dynapi/SDL_dynapi_overrides.h
src/dynapi/SDL_dynapi_procs.h
src/video/SDL_sysvideo.h
src/video/SDL_video.c
src/video/cocoa/SDL_cocoavideo.m
src/video/cocoa/SDL_cocoawindow.h
src/video/cocoa/SDL_cocoawindow.m
test/Makefile.in
test/testdragareas.c
--- a/.hgignore	Tue May 27 00:26:47 2014 -0400
+++ b/.hgignore	Tue May 27 01:27:42 2014 -0400
@@ -55,6 +55,7 @@
 test/testatomic
 test/testaudioinfo
 test/testautomation
+test/testdragareas
 test/testdraw2
 test/testerror
 test/testfile
--- a/include/SDL_video.h	Tue May 27 00:26:47 2014 -0400
+++ b/include/SDL_video.h	Tue May 27 01:27:42 2014 -0400
@@ -792,6 +792,44 @@
                                                    Uint16 * blue);
 
 /**
+ *  \brief Define regions of a window that can be used to drag it.
+ *
+ *  Normally windows are dragged by decorations provided by the system
+ *  window manager (usually, a title bar), but for some apps, it makes sense
+ *  to drag them from somewhere else inside the window itself; for example,
+ *  one might have a borderless window that wants to be draggable from any
+ *  part, or simulate its own title bar, etc.
+ *
+ *  This method designates pieces of a given window as "drag areas," which
+ *  will move the window when the user drags with his mouse, as if she had
+ *  used the titlebar.
+ *
+ *  You may specify multiple drag areas, disconnected or overlapping. This
+ *  function accepts an array of rectangles. Each call to this function will
+ *  replace any previously-defined drag areas. To disable drag areas on a
+ *  window, call this function with a NULL array of zero elements.
+ *
+ *  Drag areas do not automatically resize. If your window changes dimensions
+ *  you should plan to re-call this function with new drag areas if
+ *  appropriate.
+ *
+ *  Mouse input may not be delivered to your application if it is within
+ *  a drag area; the OS will often apply that input to moving the window and
+ *  not deliver it to the application.
+ *
+ *  Platforms that don't support this functionality will return -1
+ *  unconditionally, even if you're attempting to disable drag areas.
+ *
+ *  \param window The window to set drag areas on.
+ *  \param areas An array of SDL_Rects containing num_areas elements.
+ *  \param num_areas The number of elements in the areas parameter.
+ *  \return 0 on success, -1 on error (including unsupported).
+ */
+extern DECLSPEC int SDLCALL SDL_SetWindowDragAreas(SDL_Window * window,
+                                                   const SDL_Rect *areas,
+                                                   int num_areas);
+
+/**
  *  \brief Destroy a window.
  */
 extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window * window);
--- a/src/dynapi/SDL_dynapi_overrides.h	Tue May 27 00:26:47 2014 -0400
+++ b/src/dynapi/SDL_dynapi_overrides.h	Tue May 27 01:27:42 2014 -0400
@@ -580,3 +580,4 @@
 #define SDL_WinRTGetFSPathUTF8 SDL_WinRTGetFSPathUTF8_REAL
 #define SDL_WinRTRunApp SDL_WinRTRunApp_REAL
 #define SDL_CaptureMouse SDL_CaptureMouse_REAL
+#define SDL_SetWindowDragAreas SDL_SetWindowDragAreas_REAL
--- a/src/dynapi/SDL_dynapi_procs.h	Tue May 27 00:26:47 2014 -0400
+++ b/src/dynapi/SDL_dynapi_procs.h	Tue May 27 01:27:42 2014 -0400
@@ -613,3 +613,4 @@
 SDL_DYNAPI_PROC(int,SDL_WinRTRunApp,(int a, char **b, void *c),(a,b,c),return)
 #endif
 SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_SetWindowDragAreas,(SDL_Window *a, const SDL_Rect *b, int c),(a,b,c),return)
--- a/src/video/SDL_sysvideo.h	Tue May 27 00:26:47 2014 -0400
+++ b/src/video/SDL_sysvideo.h	Tue May 27 01:27:42 2014 -0400
@@ -97,6 +97,9 @@
 
     SDL_WindowShaper *shaper;
 
+    int num_drag_areas;
+    SDL_Rect *drag_areas;
+
     SDL_WindowUserData *data;
 
     void *driverdata;
@@ -261,6 +264,9 @@
     /* MessageBox */
     int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid);
 
+    /* Drag areas. Note that (areas) and (num_areas) are also copied to the SDL_Window for you after this call. */
+    int (*SetWindowDragAreas)(SDL_Window * window, const SDL_Rect *areas, int num_areas);
+
     /* * * */
     /* Data common to all drivers */
     SDL_bool suspend_screensaver;
--- a/src/video/SDL_video.c	Tue May 27 00:26:47 2014 -0400
+++ b/src/video/SDL_video.c	Tue May 27 01:27:42 2014 -0400
@@ -1410,6 +1410,11 @@
         SDL_SetWindowIcon(window, icon);
         SDL_FreeSurface(icon);
     }
+
+    if (window->num_drag_areas > 0) {
+        _this->SetWindowDragAreas(window, window->drag_areas, window->num_drag_areas);
+    }
+
     SDL_FinishWindowCreation(window, flags);
 
     return 0;
@@ -2305,6 +2310,8 @@
         _this->windows = window->next;
     }
 
+    SDL_free(window->drag_areas);
+
     SDL_free(window);
 }
 
@@ -3380,4 +3387,34 @@
     return SDL_TRUE;
 }
 
+int
+SDL_SetWindowDragAreas(SDL_Window * window, const SDL_Rect *_areas, int num_areas)
+{
+    SDL_Rect *areas = NULL;
+
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (!_this->SetWindowDragAreas) {
+        return SDL_Unsupported();
+    }
+
+    if (num_areas > 0) {
+        const size_t len = sizeof (SDL_Rect) * num_areas;
+        areas = (SDL_Rect *) SDL_malloc(len);
+        if (!areas) {
+            return SDL_OutOfMemory();
+        }
+        SDL_memcpy(areas, _areas, len);
+    }
+
+    if (_this->SetWindowDragAreas(window, areas, num_areas) == -1) {
+        SDL_free(areas);
+        return -1;
+    }
+
+    SDL_free(window->drag_areas);
+    window->drag_areas = areas;
+    window->num_drag_areas = num_areas;
+}
+
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/video/cocoa/SDL_cocoavideo.m	Tue May 27 00:26:47 2014 -0400
+++ b/src/video/cocoa/SDL_cocoavideo.m	Tue May 27 01:27:42 2014 -0400
@@ -108,6 +108,7 @@
     device->SetWindowGrab = Cocoa_SetWindowGrab;
     device->DestroyWindow = Cocoa_DestroyWindow;
     device->GetWindowWMInfo = Cocoa_GetWindowWMInfo;
+    device->SetWindowDragAreas = Cocoa_SetWindowDragAreas;
 
     device->shape_driver.CreateShaper = Cocoa_CreateShaper;
     device->shape_driver.SetWindowShape = Cocoa_SetWindowShape;
--- a/src/video/cocoa/SDL_cocoawindow.h	Tue May 27 00:26:47 2014 -0400
+++ b/src/video/cocoa/SDL_cocoawindow.h	Tue May 27 01:27:42 2014 -0400
@@ -45,6 +45,7 @@
     PendingWindowOperation pendingWindowOperation;
     BOOL isMoving;
     int pendingWindowWarpX, pendingWindowWarpY;
+    BOOL isDragAreaRunning;
 }
 
 -(void) listen:(SDL_WindowData *) data;
@@ -75,6 +76,9 @@
 -(void) windowDidExitFullScreen:(NSNotification *) aNotification;
 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
 
+/* See if event is in a drag area, toggle on window dragging. */
+-(BOOL) processDragArea:(NSEvent *)theEvent;
+
 /* Window event handling */
 -(void) mouseDown:(NSEvent *) theEvent;
 -(void) rightMouseDown:(NSEvent *) theEvent;
@@ -115,6 +119,7 @@
     SDL_bool inWindowMove;
     Cocoa_WindowListener *listener;
     struct SDL_VideoData *videodata;
+    NSView *dragarea;
 };
 
 extern int Cocoa_CreateWindow(_THIS, SDL_Window * window);
@@ -138,8 +143,8 @@
 extern int Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
 extern void Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void Cocoa_DestroyWindow(_THIS, SDL_Window * window);
-extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window,
-                                      struct SDL_SysWMinfo *info);
+extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info);
+extern int Cocoa_SetWindowDragAreas(SDL_Window *window, const SDL_Rect *areas, int num_areas);
 
 #endif /* _SDL_cocoawindow_h */
 
--- a/src/video/cocoa/SDL_cocoawindow.m	Tue May 27 00:26:47 2014 -0400
+++ b/src/video/cocoa/SDL_cocoawindow.m	Tue May 27 01:27:42 2014 -0400
@@ -180,6 +180,7 @@
     inFullscreenTransition = NO;
     pendingWindowOperation = PENDING_OPERATION_NONE;
     isMoving = NO;
+    isDragAreaRunning = NO;
 
     center = [NSNotificationCenter defaultCenter];
 
@@ -656,10 +657,46 @@
     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
 }
 
+- (BOOL)processDragArea:(NSEvent *)theEvent
+{
+    const int num_areas = _data->window->num_drag_areas;
+
+    SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
+    SDL_assert((num_areas > 0) || !isDragAreaRunning);
+
+    if (num_areas > 0) {  /* if no drag areas, skip this. */
+        int i;
+        const NSPoint location = [theEvent locationInWindow];
+        const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
+        const SDL_Rect *areas = _data->window->drag_areas;
+        for (i = 0; i < num_areas; i++) {
+            if (SDL_PointInRect(&point, &areas[i])) {
+                if (!isDragAreaRunning) {
+                    isDragAreaRunning = YES;
+                    [_data->nswindow setMovableByWindowBackground:YES];
+                }
+                return YES;  /* started a new drag! */
+            }
+        }
+    }
+
+    if (isDragAreaRunning) {
+        isDragAreaRunning = NO;
+        [_data->nswindow setMovableByWindowBackground:NO];
+        return YES;  /* was dragging, drop event. */
+    }
+
+    return NO;  /* not a drag area, carry on. */
+}
+
 - (void)mouseDown:(NSEvent *)theEvent
 {
     int button;
 
+    if ([self processDragArea:theEvent]) {
+        return;  /* dragging, drop event. */
+    }
+
     switch ([theEvent buttonNumber]) {
     case 0:
         if (([theEvent modifierFlags] & NSControlKeyMask) &&
@@ -698,6 +735,10 @@
 {
     int button;
 
+    if ([self processDragArea:theEvent]) {
+        return;  /* stopped dragging, drop event. */
+    }
+
     switch ([theEvent buttonNumber]) {
     case 0:
         if (wasCtrlLeft) {
@@ -737,6 +778,10 @@
     NSPoint point;
     int x, y;
 
+    if ([self processDragArea:theEvent]) {
+        return;  /* dragging, drop event. */
+    }
+
     if (mouse->relative_mode) {
         return;
     }
@@ -883,6 +928,7 @@
 
 /* The default implementation doesn't pass rightMouseDown to responder chain */
 - (void)rightMouseDown:(NSEvent *)theEvent;
+- (BOOL)mouseDownCanMoveWindow;
 @end
 
 @implementation SDLView
@@ -891,6 +937,14 @@
     [[self nextResponder] rightMouseDown:theEvent];
 }
 
+- (BOOL)mouseDownCanMoveWindow
+{
+    /* Always say YES, but this doesn't do anything until we call
+       -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
+       during mouse events when we're using a drag area. */
+    return YES;
+}
+
 - (void)resetCursorRects
 {
     [super resetCursorRects];
@@ -1544,6 +1598,12 @@
     return succeeded;
 }
 
+int
+Cocoa_SetWindowDragAreas(SDL_Window * window, const SDL_Rect *areas, int num_areas)
+{
+    return 0;  /* just succeed, the real work is done elsewhere. */
+}
+
 #endif /* SDL_VIDEO_DRIVER_COCOA */
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/test/Makefile.in	Tue May 27 00:26:47 2014 -0400
+++ b/test/Makefile.in	Tue May 27 01:27:42 2014 -0400
@@ -12,6 +12,7 @@
 	loopwave$(EXE) \
 	testaudioinfo$(EXE) \
 	testautomation$(EXE) \
+	testdragareas$(EXE) \
 	testdraw2$(EXE) \
 	testdrawchessboard$(EXE) \
 	testdropfile$(EXE) \
@@ -108,6 +109,9 @@
 testrelative$(EXE): $(srcdir)/testrelative.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
+testdragareas$(EXE): $(srcdir)/testdragareas.c
+	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
+
 testdraw2$(EXE): $(srcdir)/testdraw2.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/testdragareas.c	Tue May 27 01:27:42 2014 -0400
@@ -0,0 +1,93 @@
+#include <stdio.h>
+#include "SDL.h"
+
+/* !!! FIXME: rewrite this to be wired in to test framework. */
+
+int main(int argc, char **argv)
+{
+    int done = 0;
+    SDL_Window *window;
+    SDL_Renderer *renderer;
+
+    const SDL_Rect drag_areas[] = {
+        { 20, 20, 100, 100 },
+        { 200, 70, 100, 100 },
+        { 400, 90, 100, 100 }
+    };
+
+    const SDL_Rect *areas = drag_areas;
+    int numareas = SDL_arraysize(drag_areas);
+
+    /* !!! FIXME: check for errors. */
+    SDL_Init(SDL_INIT_VIDEO);
+    window = SDL_CreateWindow("Drag the red boxes", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_BORDERLESS);
+    renderer = SDL_CreateRenderer(window, -1, 0);
+
+    if (SDL_SetWindowDragAreas(window, areas, numareas) == -1) {
+        fprintf(stderr, "Setting drag areas failed!\n");
+        SDL_Quit();
+        return 1;
+    }
+
+    while (!done)
+    {
+        SDL_SetRenderDrawColor(renderer, 0, 0, 127, 255);
+        SDL_RenderClear(renderer);
+        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+        SDL_RenderFillRects(renderer, areas, SDL_arraysize(drag_areas));
+        SDL_RenderPresent(renderer);
+
+        SDL_Event e;
+        int nothing_to_do = 1;
+        while (SDL_PollEvent(&e)) {
+            nothing_to_do = 0;
+
+            switch (e.type)
+            {
+                case SDL_MOUSEBUTTONDOWN:
+                    printf("button down!\n");
+                    break;
+
+                case SDL_MOUSEBUTTONUP:
+                    printf("button up!\n");
+                    break;
+
+                case SDL_WINDOWEVENT:
+                    if (e.window.event == SDL_WINDOWEVENT_MOVED) {
+                        printf("Window event moved to (%d, %d)!\n", (int) e.window.data1, (int) e.window.data2);
+                    }
+                    break;
+
+                case SDL_KEYDOWN:
+                    if (e.key.keysym.sym == SDLK_ESCAPE) {
+                        done = 1;
+                    } else if (e.key.keysym.sym == SDLK_x) {
+                        if (!areas) {
+                            areas = drag_areas;
+                            numareas = SDL_arraysize(drag_areas);
+                        } else {
+                            areas = NULL;
+                            numareas = 0;
+                        }
+                        if (SDL_SetWindowDragAreas(window, areas, numareas) == -1) {
+                            fprintf(stderr, "Setting drag areas failed!\n");
+                            SDL_Quit();
+                            return 1;
+                        }
+                    }
+                    break;
+
+                case SDL_QUIT:
+                    done = 1;
+                    break;
+            }
+        }
+
+        if (nothing_to_do) {
+            SDL_Delay(50);
+        }
+    }
+
+    SDL_Quit();
+    return 0;
+}