From 3cbc83ef1174f6c63c6c03a762aa61d0caa7804c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 27 May 2014 01:27:42 -0400 Subject: [PATCH] First shot at SDL_SetWindowDragAreas(). Only Cocoa implemented right now. --- .hgignore | 1 + include/SDL_video.h | 38 +++++++++++++ src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/video/SDL_sysvideo.h | 6 ++ src/video/SDL_video.c | 37 ++++++++++++ src/video/cocoa/SDL_cocoavideo.m | 1 + src/video/cocoa/SDL_cocoawindow.h | 9 ++- src/video/cocoa/SDL_cocoawindow.m | 60 ++++++++++++++++++++ test/Makefile.in | 4 ++ test/testdragareas.c | 93 +++++++++++++++++++++++++++++++ 11 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 test/testdragareas.c diff --git a/.hgignore b/.hgignore index c04bfc01a..37ab96a3d 100644 --- a/.hgignore +++ b/.hgignore @@ -55,6 +55,7 @@ test/loopwave test/testatomic test/testaudioinfo test/testautomation +test/testdragareas test/testdraw2 test/testerror test/testfile diff --git a/include/SDL_video.h b/include/SDL_video.h index 107c8381b..f096d7ff0 100644 --- a/include/SDL_video.h +++ b/include/SDL_video.h @@ -791,6 +791,44 @@ extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window, Uint16 * green, 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. */ diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 6f74d135f..b44378d51 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 63d67e55b..232a8e927 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -613,3 +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) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 35dd940c7..17a316490 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -97,6 +97,9 @@ struct SDL_Window SDL_WindowShaper *shaper; + int num_drag_areas; + SDL_Rect *drag_areas; + SDL_WindowUserData *data; void *driverdata; @@ -261,6 +264,9 @@ 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); + /* * * */ /* Data common to all drivers */ SDL_bool suspend_screensaver; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index aa729cf3b..3476fa748 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1410,6 +1410,11 @@ SDL_RecreateWindow(SDL_Window * window, Uint32 flags) 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 @@ SDL_DestroyWindow(SDL_Window * window) _this->windows = window->next; } + SDL_free(window->drag_areas); + SDL_free(window); } @@ -3380,4 +3387,34 @@ SDL_ShouldAllowTopmost(void) 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: */ diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 6766b71bb..a24770b4d 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -108,6 +108,7 @@ Cocoa_CreateDevice(int devindex) 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; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index a4ec07d72..bfa461054 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -45,6 +45,7 @@ typedef enum PendingWindowOperation pendingWindowOperation; BOOL isMoving; int pendingWindowWarpX, pendingWindowWarpY; + BOOL isDragAreaRunning; } -(void) listen:(SDL_WindowData *) data; @@ -75,6 +76,9 @@ typedef enum -(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 @@ struct SDL_WindowData 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_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * r 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 */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 2b273f8a0..3d94b133b 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -180,6 +180,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style) inFullscreenTransition = NO; pendingWindowOperation = PENDING_OPERATION_NONE; isMoving = NO; + isDragAreaRunning = NO; center = [NSNotificationCenter defaultCenter]; @@ -656,10 +657,46 @@ SetWindowStyle(SDL_Window * window, unsigned int style) /*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 @@ SetWindowStyle(SDL_Window * window, unsigned int style) { int button; + if ([self processDragArea:theEvent]) { + return; /* stopped dragging, drop event. */ + } + switch ([theEvent buttonNumber]) { case 0: if (wasCtrlLeft) { @@ -737,6 +778,10 @@ SetWindowStyle(SDL_Window * window, unsigned int style) NSPoint point; int x, y; + if ([self processDragArea:theEvent]) { + return; /* dragging, drop event. */ + } + if (mouse->relative_mode) { return; } @@ -883,6 +928,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style) /* The default implementation doesn't pass rightMouseDown to responder chain */ - (void)rightMouseDown:(NSEvent *)theEvent; +- (BOOL)mouseDownCanMoveWindow; @end @implementation SDLView @@ -891,6 +937,14 @@ SetWindowStyle(SDL_Window * window, unsigned int style) [[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 @@ Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state) 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: */ diff --git a/test/Makefile.in b/test/Makefile.in index 7ed1a7ac9..a0476137f 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -12,6 +12,7 @@ TARGETS = \ loopwave$(EXE) \ testaudioinfo$(EXE) \ testautomation$(EXE) \ + testdragareas$(EXE) \ testdraw2$(EXE) \ testdrawchessboard$(EXE) \ testdropfile$(EXE) \ @@ -108,6 +109,9 @@ testintersections$(EXE): $(srcdir)/testintersections.c 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) diff --git a/test/testdragareas.c b/test/testdragareas.c new file mode 100644 index 000000000..776f06131 --- /dev/null +++ b/test/testdragareas.c @@ -0,0 +1,93 @@ +#include +#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; +}