From 98c03f391d2dcd91acb6c37bb072920bd256741d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 28 May 2014 01:22:47 -0400 Subject: [PATCH] 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. --- .hgignore | 2 +- include/SDL_video.h | 62 ++++++++++++---------- src/dynapi/SDL_dynapi_overrides.h | 2 +- src/dynapi/SDL_dynapi_procs.h | 2 +- src/video/SDL_sysvideo.h | 8 +-- src/video/SDL_video.c | 33 ++++-------- src/video/cocoa/SDL_cocoavideo.m | 2 +- src/video/cocoa/SDL_cocoawindow.h | 5 +- src/video/cocoa/SDL_cocoawindow.m | 32 +++++------ src/video/x11/SDL_x11events.c | 21 +++----- src/video/x11/SDL_x11video.c | 2 +- src/video/x11/SDL_x11window.c | 4 +- src/video/x11/SDL_x11window.h | 2 +- test/Makefile.in | 4 +- test/{testdragareas.c => testhittesting.c} | 41 ++++++++------ 15 files changed, 106 insertions(+), 116 deletions(-) rename test/{testdragareas.c => testhittesting.c} (78%) diff --git a/.hgignore b/.hgignore index 37ab96a3d..881425470 100644 --- a/.hgignore +++ b/.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 diff --git a/include/SDL_video.h b/include/SDL_video.h index f096d7ff0..6ccb115b3 100644 --- a/include/SDL_video.h +++ b/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. * - * \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. + * Your callback may fire at any time. + * + * \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. diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index b44378d51..ce1117529 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 232a8e927..ba78b98ad 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/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) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 17a316490..7029738a7 100644 --- a/src/video/SDL_sysvideo.h +++ b/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 */ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 3476fa748..86365d07c 100644 --- a/src/video/SDL_video.c +++ b/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: */ diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index a24770b4d..41670378f 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/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; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index bfa461054..de75092ca 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/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 */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 3d94b133b..18ae07dbb 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/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. */ } diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 3d0de138b..45b6083c1 100644 --- a/src/video/x11/SDL_x11events.c +++ b/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. */ } } diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 4cece018e..d1b043c55 100644 --- a/src/video/x11/SDL_x11video.c +++ b/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; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index 2c0c9e41b..fe883cb41 100644 --- a/src/video/x11/SDL_x11window.c +++ b/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 */ diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index 6a00c1a66..789d2f7cc 100644 --- a/src/video/x11/SDL_x11window.h +++ b/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 */ diff --git a/test/Makefile.in b/test/Makefile.in index a0476137f..227bd0eb0 100644 --- a/test/Makefile.in +++ b/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 diff --git a/test/testdragareas.c b/test/testhittesting.c similarity index 78% rename from test/testdragareas.c rename to test/testhittesting.c index 776f06131..1e3867862 100644 --- a/test/testdragareas.c +++ b/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;