Browse Source

Changed drag area API to a hit-testing API.

There were several good arguments for this: it's how Windows works with
 WM_NCHITTEST, SDL doesn't need to manage a list of rects, it allows more
 control over the regions (how do you use rects to cleanly surround a circular
 button?), the callback can be more optimized than a iterating a list of
 rects, and you don't have to send an updated list of rects whenever the
 window resizes or layout changes.
Ryan C. Gordon 11 years ago
parent
commit
98c03f391d

+ 1 - 1
.hgignore

@@ -55,7 +55,6 @@ test/loopwave
 test/testatomic
 test/testaudioinfo
 test/testautomation
-test/testdragareas
 test/testdraw2
 test/testerror
 test/testfile
@@ -64,6 +63,7 @@ test/testgesture
 test/testgl2
 test/testgles
 test/testhaptic
+test/testhittesting
 test/testiconv
 test/testime
 test/testintersections

+ 35 - 27
include/SDL_video.h

@@ -791,43 +791,51 @@ extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window,
                                                    Uint16 * green,
                                                    Uint16 * blue);
 
+typedef enum
+{
+    SDL_HITTEST_NORMAL,  /**< Region is normal. No special properties. */
+    SDL_HITTEST_DRAGGABLE,  /**< Region can drag entire window. */
+    /* !!! FIXME: resize enums here. */
+} SDL_HitTestResult;
+
+typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win,
+                                                 const SDL_Point *area,
+                                                 void *data);
+
 /**
- *  \brief Define regions of a window that can be used to drag it.
+ *  \brief Provide a callback that decides if a window region has special properties.
  *
- *  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.
+ *  Normally windows are dragged and resized by decorations provided by the
+ *  system window manager (a title bar, borders, etc), 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.
+ *  This function lets the app provide a callback that designates pieces of
+ *  a given window as special. This callback is run during event processing
+ *  if we need to tell the OS to treat a region of the window specially; the
+ *  use of this callback is known as "hit testing."
  *
  *  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.
+ *  a special area; the OS will often apply that input to moving the window or
+ *  resizing the window and not deliver it to the application.
+ *
+ *  Specifying NULL for a callback disables hit-testing. Hit-testing is
+ *  disabled by default.
  *
  *  Platforms that don't support this functionality will return -1
- *  unconditionally, even if you're attempting to disable drag areas.
+ *  unconditionally, even if you're attempting to disable hit-testing.
+ *
+ *  Your callback may fire at any time.
  *
- *  \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.
+ *  \param window The window to set hit-testing on.
+ *  \param callback The callback to call when doing a hit-test.
+ *  \param callback_data An app-defined void pointer passed to the callback.
  *  \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);
+extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window * window,
+                                                 SDL_HitTest callback,
+                                                 void *callback_data);
 
 /**
  *  \brief Destroy a window.

+ 1 - 1
src/dynapi/SDL_dynapi_overrides.h

@@ -580,4 +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
+#define SDL_SetWindowHitTest SDL_SetWindowHitTest_REAL

+ 1 - 1
src/dynapi/SDL_dynapi_procs.h

@@ -613,4 +613,4 @@ SDL_DYNAPI_PROC(const char*,SDL_WinRTGetFSPathUTF8,(SDL_WinRT_Path a),(a),return
 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)
+SDL_DYNAPI_PROC(int,SDL_SetWindowHitTest,(SDL_Window *a, SDL_HitTest b, void *c),(a,b,c),return)

+ 4 - 4
src/video/SDL_sysvideo.h

@@ -97,8 +97,8 @@ struct SDL_Window
 
     SDL_WindowShaper *shaper;
 
-    int num_drag_areas;
-    SDL_Rect *drag_areas;
+    SDL_HitTest hit_test;
+    void *hit_test_data;
 
     SDL_WindowUserData *data;
 
@@ -264,8 +264,8 @@ struct SDL_VideoDevice
     /* 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);
+    /* Hit-testing */
+    int (*SetWindowHitTest)(SDL_Window * window, SDL_bool enabled);
 
     /* * * */
     /* Data common to all drivers */

+ 9 - 24
src/video/SDL_video.c

@@ -1411,8 +1411,8 @@ SDL_RecreateWindow(SDL_Window * window, Uint32 flags)
         SDL_FreeSurface(icon);
     }
 
-    if (window->num_drag_areas > 0) {
-        _this->SetWindowDragAreas(window, window->drag_areas, window->num_drag_areas);
+    if (window->hit_test > 0) {
+        _this->SetWindowHitTest(window, SDL_TRUE);
     }
 
     SDL_FinishWindowCreation(window, flags);
@@ -2310,8 +2310,6 @@ SDL_DestroyWindow(SDL_Window * window)
         _this->windows = window->next;
     }
 
-    SDL_free(window->drag_areas);
-
     SDL_free(window);
 }
 
@@ -3388,33 +3386,20 @@ SDL_ShouldAllowTopmost(void)
 }
 
 int
-SDL_SetWindowDragAreas(SDL_Window * window, const SDL_Rect *_areas, int num_areas)
+SDL_SetWindowHitTest(SDL_Window * window, SDL_HitTest callback, void *userdata)
 {
-    SDL_Rect *areas = NULL;
-
     CHECK_WINDOW_MAGIC(window, -1);
 
-    if (!_this->SetWindowDragAreas) {
+    if (!_this->SetWindowHitTest) {
         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);
+    } else if (_this->SetWindowHitTest(window, callback != NULL) == -1) {
         return -1;
     }
 
-    SDL_free(window->drag_areas);
-    window->drag_areas = areas;
-    window->num_drag_areas = num_areas;
+    window->hit_test = callback;
+    window->hit_test_data = userdata;
+
+    return 0;
 }
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 1 - 1
src/video/cocoa/SDL_cocoavideo.m

@@ -108,7 +108,7 @@ Cocoa_CreateDevice(int devindex)
     device->SetWindowGrab = Cocoa_SetWindowGrab;
     device->DestroyWindow = Cocoa_DestroyWindow;
     device->GetWindowWMInfo = Cocoa_GetWindowWMInfo;
-    device->SetWindowDragAreas = Cocoa_SetWindowDragAreas;
+    device->SetWindowHitTest = Cocoa_SetWindowHitTest;
 
     device->shape_driver.CreateShaper = Cocoa_CreateShaper;
     device->shape_driver.SetWindowShape = Cocoa_SetWindowShape;

+ 2 - 3
src/video/cocoa/SDL_cocoawindow.h

@@ -77,7 +77,7 @@ typedef enum
 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
 
 /* See if event is in a drag area, toggle on window dragging. */
--(BOOL) processDragArea:(NSEvent *)theEvent;
+-(BOOL) processHitTest:(NSEvent *)theEvent;
 
 /* Window event handling */
 -(void) mouseDown:(NSEvent *) theEvent;
@@ -119,7 +119,6 @@ struct SDL_WindowData
     SDL_bool inWindowMove;
     Cocoa_WindowListener *listener;
     struct SDL_VideoData *videodata;
-    NSView *dragarea;
 };
 
 extern int Cocoa_CreateWindow(_THIS, SDL_Window * window);
@@ -144,7 +143,7 @@ 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 int Cocoa_SetWindowDragAreas(SDL_Window *window, const SDL_Rect *areas, int num_areas);
+extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 
 #endif /* _SDL_cocoawindow_h */
 

+ 13 - 19
src/video/cocoa/SDL_cocoawindow.m

@@ -657,26 +657,20 @@ SetWindowStyle(SDL_Window * window, unsigned int style)
     /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
 }
 
-- (BOOL)processDragArea:(NSEvent *)theEvent
+- (BOOL)processHitTest:(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;
+    if (_data->window->hit_test) {  /* if no hit-test, skip this. */
         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! */
+        const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
+        if (rc == SDL_HITTEST_DRAGGABLE) {
+            if (!isDragAreaRunning) {
+                isDragAreaRunning = YES;
+                [_data->nswindow setMovableByWindowBackground:YES];
             }
+            return YES;  /* dragging! */
         }
     }
 
@@ -686,14 +680,14 @@ SetWindowStyle(SDL_Window * window, unsigned int style)
         return YES;  /* was dragging, drop event. */
     }
 
-    return NO;  /* not a drag area, carry on. */
+    return NO;  /* not a special area, carry on. */
 }
 
 - (void)mouseDown:(NSEvent *)theEvent
 {
     int button;
 
-    if ([self processDragArea:theEvent]) {
+    if ([self processHitTest:theEvent]) {
         return;  /* dragging, drop event. */
     }
 
@@ -735,7 +729,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style)
 {
     int button;
 
-    if ([self processDragArea:theEvent]) {
+    if ([self processHitTest:theEvent]) {
         return;  /* stopped dragging, drop event. */
     }
 
@@ -778,7 +772,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style)
     NSPoint point;
     int x, y;
 
-    if ([self processDragArea:theEvent]) {
+    if ([self processHitTest:theEvent]) {
         return;  /* dragging, drop event. */
     }
 
@@ -1599,7 +1593,7 @@ Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state)
 }
 
 int
-Cocoa_SetWindowDragAreas(SDL_Window * window, const SDL_Rect *areas, int num_areas)
+Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
 {
     return 0;  /* just succeed, the real work is done elsewhere. */
 }

+ 8 - 13
src/video/x11/SDL_x11events.c

@@ -303,21 +303,16 @@ InitiateWindowMove(_THIS, const SDL_WindowData *data, const SDL_Point *point)
 }
 
 static SDL_bool
-ProcessDragArea(_THIS, const SDL_WindowData *data, const XEvent *xev)
+ProcessHitTest(_THIS, const SDL_WindowData *data, const XEvent *xev)
 {
-    const SDL_Window *window = data->window;
-    const int num_areas = window->num_drag_areas;
+    SDL_Window *window = data->window;
 
-    if (num_areas > 0) {
+    if (window->hit_test) {
         const SDL_Point point = { xev->xbutton.x, xev->xbutton.y };
-        const SDL_Rect *areas = window->drag_areas;
-        int i;
-
-        for (i = 0; i < num_areas; i++) {
-            if (SDL_PointInRect(&point, &areas[i])) {
-                InitiateWindowMove(_this, data, &point);
-                return SDL_TRUE;  /* dragging, drop this event. */
-            }
+        const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
+        if (rc == SDL_HITTEST_DRAGGABLE) {
+            InitiateWindowMove(_this, data, &point);
+            return SDL_TRUE;  /* dragging, drop this event. */
         }
     }
 
@@ -762,7 +757,7 @@ X11_DispatchEvent(_THIS)
                 SDL_SendMouseWheel(data->window, 0, 0, ticks);
             } else {
                 if(xevent.xbutton.button == Button1) {
-                    if (ProcessDragArea(_this, data, &xevent)) {
+                    if (ProcessHitTest(_this, data, &xevent)) {
                         break;  /* don't pass this event on to app. */
                     }
                 }

+ 1 - 1
src/video/x11/SDL_x11video.c

@@ -457,7 +457,7 @@ X11_CreateDevice(int devindex)
     device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
     device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
     device->GetWindowWMInfo = X11_GetWindowWMInfo;
-    device->SetWindowDragAreas = X11_SetWindowDragAreas;
+    device->SetWindowHitTest = X11_SetWindowHitTest;
 
     device->shape_driver.CreateShaper = X11_CreateShaper;
     device->shape_driver.SetWindowShape = X11_SetWindowShape;

+ 2 - 2
src/video/x11/SDL_x11window.c

@@ -1445,9 +1445,9 @@ X11_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
 }
 
 int
-X11_SetWindowDragAreas(SDL_Window *window, const SDL_Rect *areas, int num_areas)
+X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled)
 {
-    return 0; // nothing to do, will be handled in event handler
+    return 0;  /* just succeed, the real work is done elsewhere. */
 }
 
 #endif /* SDL_VIDEO_DRIVER_X11 */

+ 1 - 1
src/video/x11/SDL_x11window.h

@@ -93,7 +93,7 @@ extern void X11_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void X11_DestroyWindow(_THIS, SDL_Window * window);
 extern SDL_bool X11_GetWindowWMInfo(_THIS, SDL_Window * window,
                                     struct SDL_SysWMinfo *info);
-extern int X11_SetWindowDragAreas(SDL_Window *window, const SDL_Rect *areas, int num_areas);
+extern int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 
 #endif /* _SDL_x11window_h */
 

+ 2 - 2
test/Makefile.in

@@ -12,7 +12,6 @@ TARGETS = \
 	loopwave$(EXE) \
 	testaudioinfo$(EXE) \
 	testautomation$(EXE) \
-	testdragareas$(EXE) \
 	testdraw2$(EXE) \
 	testdrawchessboard$(EXE) \
 	testdropfile$(EXE) \
@@ -24,6 +23,7 @@ TARGETS = \
 	testgles$(EXE) \
 	testgles2$(EXE) \
 	testhaptic$(EXE) \
+	testhittesting$(EXE) \
 	testrumble$(EXE) \
 	testhotplug$(EXE) \
 	testthread$(EXE) \
@@ -109,7 +109,7 @@ testintersections$(EXE): $(srcdir)/testintersections.c
 testrelative$(EXE): $(srcdir)/testrelative.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
-testdragareas$(EXE): $(srcdir)/testdragareas.c
+testhittesting$(EXE): $(srcdir)/testhittesting.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
 testdraw2$(EXE): $(srcdir)/testdraw2.c

+ 25 - 16
test/testdragareas.c → test/testhittesting.c

@@ -3,28 +3,42 @@
 
 /* !!! FIXME: rewrite this to be wired in to test framework. */
 
+const SDL_Rect drag_areas[] = {
+    { 20, 20, 100, 100 },
+    { 200, 70, 100, 100 },
+    { 400, 90, 100, 100 }
+};
+
+static const SDL_Rect *areas = drag_areas;
+static int numareas = SDL_arraysize(drag_areas);
+
+static SDL_HitTestResult
+hitTest(SDL_Window *window, const SDL_Point *pt, void *data)
+{
+    int i;
+    for (i = 0; i < numareas; i++) {
+        if (SDL_PointInRect(pt, &areas[i])) {
+            return SDL_HITTEST_DRAGGABLE;
+        }
+    }
+
+    return SDL_HITTEST_NORMAL;
+}
+
+
 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");
+    if (SDL_SetWindowHitTest(window, hitTest, NULL) == -1) {
+        fprintf(stderr, "Enabling hit-testing failed!\n");
         SDL_Quit();
         return 1;
     }
@@ -69,11 +83,6 @@ int main(int argc, char **argv)
                             areas = NULL;
                             numareas = 0;
                         }
-                        if (SDL_SetWindowDragAreas(window, areas, numareas) == -1) {
-                            fprintf(stderr, "Setting drag areas failed!\n");
-                            SDL_Quit();
-                            return 1;
-                        }
                     }
                     break;