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.
This commit is contained in:
Ryan C. Gordon 2014-05-28 01:22:47 -04:00
parent 7a4ddcd8c6
commit 98c03f391d
15 changed files with 106 additions and 116 deletions

View File

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

View File

@ -791,43 +791,51 @@ extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window,
Uint16 * green, Uint16 * green,
Uint16 * blue); 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 * Normally windows are dragged and resized by decorations provided by the
* window manager (usually, a title bar), but for some apps, it makes sense * system window manager (a title bar, borders, etc), but for some apps, it
* to drag them from somewhere else inside the window itself; for example, * makes sense to drag them from somewhere else inside the window itself; for
* one might have a borderless window that wants to be draggable from any * example, one might have a borderless window that wants to be draggable
* part, or simulate its own title bar, etc. * from any part, or simulate its own title bar, etc.
* *
* This method designates pieces of a given window as "drag areas," which * This function lets the app provide a callback that designates pieces of
* will move the window when the user drags with his mouse, as if she had * a given window as special. This callback is run during event processing
* used the titlebar. * 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."
* 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 * 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 * a special area; the OS will often apply that input to moving the window or
* not deliver it to the application. * 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 * 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. * Your callback may fire at any time.
* \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). * \return 0 on success, -1 on error (including unsupported).
*/ */
extern DECLSPEC int SDLCALL SDL_SetWindowDragAreas(SDL_Window * window, extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window * window,
const SDL_Rect *areas, SDL_HitTest callback,
int num_areas); void *callback_data);
/** /**
* \brief Destroy a window. * \brief Destroy a window.

View File

@ -580,4 +580,4 @@
#define SDL_WinRTGetFSPathUTF8 SDL_WinRTGetFSPathUTF8_REAL #define SDL_WinRTGetFSPathUTF8 SDL_WinRTGetFSPathUTF8_REAL
#define SDL_WinRTRunApp SDL_WinRTRunApp_REAL #define SDL_WinRTRunApp SDL_WinRTRunApp_REAL
#define SDL_CaptureMouse SDL_CaptureMouse_REAL #define SDL_CaptureMouse SDL_CaptureMouse_REAL
#define SDL_SetWindowDragAreas SDL_SetWindowDragAreas_REAL #define SDL_SetWindowHitTest SDL_SetWindowHitTest_REAL

View File

@ -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) SDL_DYNAPI_PROC(int,SDL_WinRTRunApp,(int a, char **b, void *c),(a,b,c),return)
#endif #endif
SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return) 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1445,9 +1445,9 @@ X11_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
} }
int 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 */ #endif /* SDL_VIDEO_DRIVER_X11 */

View File

@ -93,7 +93,7 @@ extern void X11_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
extern void X11_DestroyWindow(_THIS, SDL_Window * window); extern void X11_DestroyWindow(_THIS, SDL_Window * window);
extern SDL_bool X11_GetWindowWMInfo(_THIS, SDL_Window * window, extern SDL_bool X11_GetWindowWMInfo(_THIS, SDL_Window * window,
struct SDL_SysWMinfo *info); 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 */ #endif /* _SDL_x11window_h */

View File

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

View File

@ -3,28 +3,42 @@
/* !!! FIXME: rewrite this to be wired in to test framework. */ /* !!! 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 main(int argc, char **argv)
{ {
int done = 0; int done = 0;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; 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. */ /* !!! FIXME: check for errors. */
SDL_Init(SDL_INIT_VIDEO); SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("Drag the red boxes", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_BORDERLESS); window = SDL_CreateWindow("Drag the red boxes", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_BORDERLESS);
renderer = SDL_CreateRenderer(window, -1, 0); renderer = SDL_CreateRenderer(window, -1, 0);
if (SDL_SetWindowDragAreas(window, areas, numareas) == -1) { if (SDL_SetWindowHitTest(window, hitTest, NULL) == -1) {
fprintf(stderr, "Setting drag areas failed!\n"); fprintf(stderr, "Enabling hit-testing failed!\n");
SDL_Quit(); SDL_Quit();
return 1; return 1;
} }
@ -69,11 +83,6 @@ int main(int argc, char **argv)
areas = NULL; areas = NULL;
numareas = 0; numareas = 0;
} }
if (SDL_SetWindowDragAreas(window, areas, numareas) == -1) {
fprintf(stderr, "Setting drag areas failed!\n");
SDL_Quit();
return 1;
}
} }
break; break;