diff --git a/.hgignore b/.hgignore index d25a1d918..e61b7d96a 100644 --- a/.hgignore +++ b/.hgignore @@ -84,6 +84,7 @@ test/testgesture test/testgl2 test/testgles test/testhaptic +test/testhittesting test/testiconv test/testime test/testintersections diff --git a/include/SDL_mouse.h b/include/SDL_mouse.h index 69eb8545f..e71a0c9e5 100644 --- a/include/SDL_mouse.h +++ b/include/SDL_mouse.h @@ -77,6 +77,31 @@ extern DECLSPEC SDL_Window * SDLCALL SDL_GetMouseFocus(void); */ extern DECLSPEC Uint32 SDLCALL SDL_GetMouseState(int *x, int *y); +/** + * \brief Get the current state of the mouse, in relation to the desktop + * + * This works just like SDL_GetMouseState(), but the coordinates will be + * reported relative to the top-left of the desktop. This can be useful if + * you need to track the mouse outside of a specific window and + * SDL_CaptureMouse() doesn't fit your needs. For example, it could be + * useful if you need to track the mouse while dragging a window, where + * coordinates relative to a window might not be in sync at all times. + * + * \note SDL_GetMouseState() returns the mouse position as SDL understands + * it from the last pump of the event queue. This function, however, + * queries the OS for the current mouse position, and as such, might + * be a slightly less efficient function. Unless you know what you're + * doing and have a good reason to use this function, you probably want + * SDL_GetMouseState() instead. + * + * \param x Returns the current X coord, relative to the desktop. Can be NULL. + * \param y Returns the current Y coord, relative to the desktop. Can be NULL. + * \return The current button state as a bitmask, which can be tested using the SDL_BUTTON(X) macros. + * + * \sa SDL_GetMouseState + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetGlobalMouseState(int *x, int *y); + /** * \brief Retrieve the relative state of the mouse. * @@ -126,6 +151,37 @@ extern DECLSPEC void SDLCALL SDL_WarpMouseGlobal(int x, int y); */ extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled); +/** + * \brief Capture the mouse, to track input outside an SDL window. + * + * \param enabled Whether or not to enable capturing + * + * Capturing enables your app to obtain mouse events globally, instead of + * just within your window. Not all video targets support this function. + * When capturing is enabled, the current window will get all mouse events, + * but unlike relative mode, no change is made to the cursor and it is + * not restrained to your window. + * + * This function may also deny mouse input to other windows--both those in + * your application and others on the system--so you should use this + * function sparingly, and in small bursts. For example, you might want to + * track the mouse while the user is dragging something, until the user + * releases a mouse button. It is not recommended that you capture the mouse + * for long periods of time, such as the entire time your app is running. + * + * While captured, mouse events still report coordinates relative to the + * current (foreground) window, but those coordinates may be outside the + * bounds of the window (including negative values). Capturing is only + * allowed for the foreground window. If the window loses focus while + * capturing, the capture will be disabled automatically. + * + * While capturing is enabled, the current window will have the + * SDL_WINDOW_MOUSE_CAPTURE flag set. + * + * \return 0 on success, or -1 if not supported. + */ +extern DECLSPEC int SDLCALL SDL_CaptureMouse(SDL_bool enabled); + /** * \brief Query whether relative mouse mode is enabled. * diff --git a/include/SDL_rect.h b/include/SDL_rect.h index 0a95a3344..6bfce3115 100644 --- a/include/SDL_rect.h +++ b/include/SDL_rect.h @@ -43,6 +43,7 @@ extern "C" { * \brief The structure that defines a point * * \sa SDL_EnclosePoints + * \sa SDL_PointInRect */ typedef struct SDL_Point { @@ -66,6 +67,15 @@ typedef struct SDL_Rect int w, h; } SDL_Rect; +/** + * \brief Returns true if point resides inside a rectangle. + */ +SDL_FORCE_INLINE SDL_bool SDL_PointInRect(const SDL_Point *p, const SDL_Rect *r) +{ + return ( (p->x >= r->x) && (p->x < (r->x + r->w)) && + (p->y >= r->y) && (p->y < (r->y + r->h)) ) ? SDL_TRUE : SDL_FALSE; +} + /** * \brief Returns true if the rectangle has no area. */ diff --git a/include/SDL_video.h b/include/SDL_video.h index 607062cb5..4a2fb0458 100644 --- a/include/SDL_video.h +++ b/include/SDL_video.h @@ -108,7 +108,8 @@ typedef enum SDL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has mouse focus */ SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ), SDL_WINDOW_FOREIGN = 0x00000800, /**< window not created by SDL */ - SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000 /**< window should be created in high-DPI mode if supported */ + SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000, /**< window should be created in high-DPI mode if supported */ + SDL_WINDOW_MOUSE_CAPTURE = 0x00004000 /**< window has mouse captured (unrelated to INPUT_GRABBED) */ } SDL_WindowFlags; /** @@ -790,6 +791,75 @@ extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window, Uint16 * green, Uint16 * blue); +/** + * \brief Possible return values from the SDL_HitTest callback. + * + * \sa SDL_HitTest + */ +typedef enum +{ + SDL_HITTEST_NORMAL, /**< Region is normal. No special properties. */ + SDL_HITTEST_DRAGGABLE, /**< Region can drag entire window. */ + SDL_HITTEST_RESIZE_TOPLEFT, + SDL_HITTEST_RESIZE_TOP, + SDL_HITTEST_RESIZE_TOPRIGHT, + SDL_HITTEST_RESIZE_RIGHT, + SDL_HITTEST_RESIZE_BOTTOMRIGHT, + SDL_HITTEST_RESIZE_BOTTOM, + SDL_HITTEST_RESIZE_BOTTOMLEFT, + SDL_HITTEST_RESIZE_LEFT +} SDL_HitTestResult; + +/** + * \brief Callback used for hit-testing. + * + * \sa SDL_SetWindowHitTest + */ +typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win, + const SDL_Point *area, + void *data); + +/** + * \brief Provide a callback that decides if a window region has special properties. + * + * 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 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 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 hit-testing. + * + * Your callback may fire at any time, and its firing does not indicate any + * specific behavior (for example, on Windows, this certainly might fire + * when the OS is deciding whether to drag your window, but it fires for lots + * of other reasons, too, some unrelated to anything you probably care about + * _and when the mouse isn't actually at the location it is testing_). + * Since this can fire at any time, you should try to keep your callback + * efficient, devoid of allocations, etc. + * + * \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_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 40ae7dd3b..9aac2c963 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -584,3 +584,6 @@ #define SDL_sqrtf SDL_sqrtf_REAL #define SDL_tan SDL_tan_REAL #define SDL_tanf SDL_tanf_REAL +#define SDL_CaptureMouse SDL_CaptureMouse_REAL +#define SDL_SetWindowHitTest SDL_SetWindowHitTest_REAL +#define SDL_GetGlobalMouseState SDL_GetGlobalMouseState_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 217fcd57c..bdb3cc4c6 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -616,3 +616,6 @@ SDL_DYNAPI_PROC(void,SDL_WarpMouseGlobal,(int a, int b),(a,b),) SDL_DYNAPI_PROC(float,SDL_sqrtf,(float a),(a),return) SDL_DYNAPI_PROC(double,SDL_tan,(double a),(a),return) SDL_DYNAPI_PROC(float,SDL_tanf,(float a),(a),return) +SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return) +SDL_DYNAPI_PROC(int,SDL_SetWindowHitTest,(SDL_Window *a, SDL_HitTest b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(Uint32,SDL_GetGlobalMouseState,(int *a, int *b),(a,b),return) diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c index d5443893b..ed4043e9c 100644 --- a/src/events/SDL_keyboard.c +++ b/src/events/SDL_keyboard.c @@ -25,6 +25,7 @@ #include "SDL_timer.h" #include "SDL_events.h" #include "SDL_events_c.h" +#include "SDL_assert.h" #include "../video/SDL_sysvideo.h" @@ -619,6 +620,16 @@ SDL_SetKeyboardFocus(SDL_Window * window) /* See if the current window has lost focus */ if (keyboard->focus && keyboard->focus != window) { + + /* new window shouldn't think it has mouse captured. */ + SDL_assert(!window || !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)); + + /* old window must lose an existing mouse capture. */ + if (keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE) { + SDL_CaptureMouse(SDL_FALSE); /* drop the capture. */ + SDL_assert(!(keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE)); + } + SDL_SendWindowEvent(keyboard->focus, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c index 4ee68844d..5ad3a72ca 100644 --- a/src/events/SDL_mouse.c +++ b/src/events/SDL_mouse.c @@ -140,14 +140,14 @@ static SDL_bool SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate) { SDL_Mouse *mouse = SDL_GetMouse(); - int w, h; - SDL_bool inWindow; + SDL_bool inWindow = SDL_TRUE; - SDL_GetWindowSize(window, &w, &h); - if (x < 0 || y < 0 || x >= w || y >= h) { - inWindow = SDL_FALSE; - } else { - inWindow = SDL_TRUE; + if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + if (x < 0 || y < 0 || x >= w || y >= h) { + inWindow = SDL_FALSE; + } } /* Linux doesn't give you mouse events outside your window unless you grab @@ -246,24 +246,29 @@ SDL_PrivateSendMouseMotion(SDL_Window * window, SDL_MouseID mouseID, int relativ mouse->y += yrel; } - /* !!! FIXME: shouldn't this be (window) instead of (mouse->focus)? */ - SDL_GetWindowSize(mouse->focus, &x_max, &y_max); - --x_max; - --y_max; + /* make sure that the pointers find themselves inside the windows, + unless we have the mouse captured. */ + if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) { + int x_max = 0, y_max = 0; - /* make sure that the pointers find themselves inside the windows */ - if (mouse->x > x_max) { - mouse->x = x_max; - } - if (mouse->x < 0) { - mouse->x = 0; - } + // !!! FIXME: shouldn't this be (window) instead of (mouse->focus)? + SDL_GetWindowSize(mouse->focus, &x_max, &y_max); + --x_max; + --y_max; - if (mouse->y > y_max) { - mouse->y = y_max; - } - if (mouse->y < 0) { - mouse->y = 0; + if (mouse->x > x_max) { + mouse->x = x_max; + } + if (mouse->x < 0) { + mouse->x = 0; + } + + if (mouse->y > y_max) { + mouse->y = y_max; + } + if (mouse->y < 0) { + mouse->y = 0; + } } mouse->xdelta += xrel; @@ -426,6 +431,7 @@ SDL_MouseQuit(void) SDL_Cursor *cursor, *next; SDL_Mouse *mouse = SDL_GetMouse(); + SDL_CaptureMouse(SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE); SDL_ShowCursor(1); @@ -477,16 +483,42 @@ SDL_GetRelativeMouseState(int *x, int *y) return mouse->buttonstate; } +Uint32 +SDL_GetGlobalMouseState(int *x, int *y) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + int tmpx, tmpy; + + /* make sure these are never NULL for the backend implementations... */ + if (!x) { + x = &tmpx; + } + if (!y) { + y = &tmpy; + } + + *x = *y = 0; + + if (!mouse->GetGlobalMouseState) { + SDL_assert(0 && "This should really be implemented for every target."); + return 0; + } + + return mouse->GetGlobalMouseState(x, y); +} + void SDL_WarpMouseInWindow(SDL_Window * window, int x, int y) { SDL_Mouse *mouse = SDL_GetMouse(); - if (window == NULL) + if (window == NULL) { window = mouse->focus; + } - if (window == NULL) + if (window == NULL) { return; + } if (mouse->WarpMouse) { mouse->WarpMouse(window, x, y); diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h index 7157a1c46..df95e1e52 100644 --- a/src/events/SDL_mouse_c.h +++ b/src/events/SDL_mouse_c.h @@ -66,6 +66,12 @@ typedef struct /* Set relative mode */ int (*SetRelativeMouseMode) (SDL_bool enabled); + /* Set mouse capture */ + int (*CaptureMouse) (SDL_Window * window); + + /* Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call. */ + Uint32 (*GetGlobalMouseState) (int *x, int *y); + /* Data common to all mice */ SDL_MouseID mouseID; SDL_Window *focus; diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 6fa984b6a..a2fe31984 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1379,6 +1379,14 @@ SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done) } } } + if (withShift) { + SDL_Window *current_win = SDL_GetKeyboardFocus(); + if (current_win) { + const SDL_bool shouldCapture = (SDL_GetWindowFlags(current_win) & SDL_WINDOW_MOUSE_CAPTURE) == 0; + const int rc = SDL_CaptureMouse(shouldCapture); + SDL_Log("%sapturing mouse %s!\n", shouldCapture ? "C" : "Unc", (rc == 0) ? "succeeded" : "failed"); + } + } break; case SDLK_v: if (withControl) { @@ -1478,6 +1486,19 @@ SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done) } } break; + case SDLK_a: + if (withControl) { + /* Ctrl-A reports absolute mouse position. */ + int x, y; + const Uint32 mask = SDL_GetGlobalMouseState(&x, &y); + SDL_Log("ABSOLUTE MOUSE: (%d, %d)%s%s%s%s%s\n", x, y, + (mask & SDL_BUTTON_LMASK) ? " [LBUTTON]" : "", + (mask & SDL_BUTTON_MMASK) ? " [MBUTTON]" : "", + (mask & SDL_BUTTON_RMASK) ? " [RBUTTON]" : "", + (mask & SDL_BUTTON_X1MASK) ? " [X2BUTTON]" : "", + (mask & SDL_BUTTON_X2MASK) ? " [X2BUTTON]" : ""); + } + break; case SDLK_0: if (withControl) { SDL_Window *window = SDL_GetWindowFromID(event->key.windowID); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index c2abe847f..992b4f364 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -97,6 +97,9 @@ struct SDL_Window SDL_WindowShaper *shaper; + SDL_HitTest hit_test; + void *hit_test_data; + SDL_WindowUserData *data; void *driverdata; @@ -261,6 +264,9 @@ struct SDL_VideoDevice /* MessageBox */ int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid); + /* Hit-testing */ + int (*SetWindowHitTest)(SDL_Window * window, SDL_bool enabled); + /* * * */ /* Data common to all drivers */ SDL_bool suspend_screensaver; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 11492f608..e50edc1bd 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1428,6 +1428,11 @@ SDL_RecreateWindow(SDL_Window * window, Uint32 flags) SDL_SetWindowIcon(window, icon); SDL_FreeSurface(icon); } + + if (window->hit_test) { + _this->SetWindowHitTest(window, SDL_TRUE); + } + SDL_FinishWindowCreation(window, flags); return 0; @@ -3292,12 +3297,17 @@ SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) int retval = -1; SDL_bool relative_mode; int show_cursor_prev; + SDL_bool mouse_captured; + SDL_Window *current_window; if (!messageboxdata) { return SDL_InvalidParamError("messageboxdata"); } + current_window = SDL_GetKeyboardFocus(); + mouse_captured = current_window && ((SDL_GetWindowFlags(current_window) & SDL_WINDOW_MOUSE_CAPTURE) != 0); relative_mode = SDL_GetRelativeMouseMode(); + SDL_CaptureMouse(SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE); show_cursor_prev = SDL_ShowCursor(1); @@ -3349,6 +3359,13 @@ SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) SDL_SetError("No message system available"); } + if (current_window) { + SDL_RaiseWindow(current_window); + if (mouse_captured) { + SDL_CaptureMouse(SDL_TRUE); + } + } + SDL_ShowCursor(show_cursor_prev); SDL_SetRelativeMouseMode(relative_mode); @@ -3391,4 +3408,21 @@ SDL_ShouldAllowTopmost(void) return SDL_TRUE; } +int +SDL_SetWindowHitTest(SDL_Window * window, SDL_HitTest callback, void *userdata) +{ + CHECK_WINDOW_MAGIC(window, -1); + + if (!_this->SetWindowHitTest) { + return SDL_Unsupported(); + } else if (_this->SetWindowHitTest(window, callback != NULL) == -1) { + return -1; + } + + 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_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index d231684a2..287dcd719 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -307,6 +307,39 @@ Cocoa_SetRelativeMouseMode(SDL_bool enabled) return 0; } +static int +Cocoa_CaptureMouse(SDL_Window *window) +{ + /* our Cocoa event code already tracks the mouse outside the window, + so all we have to do here is say "okay" and do what we always do. */ + return 0; +} + +static Uint32 +Cocoa_GetGlobalMouseState(int *x, int *y) +{ + const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; + const NSPoint cocoaLocation = [NSEvent mouseLocation]; + Uint32 retval = 0; + + for (NSScreen *screen in [NSScreen screens]) { + NSRect frame = [screen frame]; + if (NSPointInRect(cocoaLocation, frame)) { + *x = (int) cocoaLocation.x; + *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y); + break; + } + } + + retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; + retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; + retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; + retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; + retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; + + return retval; +} + void Cocoa_InitMouse(_THIS) { @@ -321,6 +354,8 @@ Cocoa_InitMouse(_THIS) mouse->WarpMouse = Cocoa_WarpMouse; mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; + mouse->CaptureMouse = Cocoa_CaptureMouse; + mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 6766b71bb..41670378f 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->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 a4ec07d72..de75092ca 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) processHitTest:(NSEvent *)theEvent; + /* Window event handling */ -(void) mouseDown:(NSEvent *) theEvent; -(void) rightMouseDown:(NSEvent *) theEvent; @@ -138,8 +142,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_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 47051acfa..383468888 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -192,6 +192,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style) inFullscreenTransition = NO; pendingWindowOperation = PENDING_OPERATION_NONE; isMoving = NO; + isDragAreaRunning = NO; center = [NSNotificationCenter defaultCenter]; @@ -663,10 +664,48 @@ SetWindowStyle(SDL_Window * window, unsigned int style) /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/ } +/* We'll respond to selectors by doing nothing so we don't beep. + * The escape key gets converted to a "cancel" selector, etc. + */ +- (void)doCommandBySelector:(SEL)aSelector +{ + /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/ +} + +- (BOOL)processHitTest:(NSEvent *)theEvent +{ + SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]); + + 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_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! */ + } + } + + if (isDragAreaRunning) { + isDragAreaRunning = NO; + [_data->nswindow setMovableByWindowBackground:NO]; + return YES; /* was dragging, drop event. */ + } + + return NO; /* not a special area, carry on. */ +} + - (void)mouseDown:(NSEvent *)theEvent { int button; + if ([self processHitTest:theEvent]) { + return; /* dragging, drop event. */ + } + switch ([theEvent buttonNumber]) { case 0: if (([theEvent modifierFlags] & NSControlKeyMask) && @@ -705,6 +744,10 @@ SetWindowStyle(SDL_Window * window, unsigned int style) { int button; + if ([self processHitTest:theEvent]) { + return; /* stopped dragging, drop event. */ + } + switch ([theEvent buttonNumber]) { case 0: if (wasCtrlLeft) { @@ -744,6 +787,10 @@ SetWindowStyle(SDL_Window * window, unsigned int style) NSPoint point; int x, y; + if ([self processHitTest:theEvent]) { + return; /* dragging, drop event. */ + } + if (mouse->relative_mode) { return; } @@ -752,8 +799,8 @@ SetWindowStyle(SDL_Window * window, unsigned int style) x = (int)point.x; y = (int)(window->h - point.y); - if (x < 0 || x >= window->w || y < 0 || y >= window->h) { - if (window->flags & SDL_WINDOW_INPUT_GRABBED) { + if (window->flags & SDL_WINDOW_INPUT_GRABBED) { + if (x < 0 || x >= window->w || y < 0 || y >= window->h) { if (x < 0) { x = 0; } else if (x >= window->w) { @@ -890,6 +937,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 @@ -898,6 +946,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]; @@ -995,6 +1051,7 @@ SetupWindowData(_THIS, SDL_Window * window, NSWindow *nswindow, SDL_bool created /* All done! */ [pool release]; + window->driverdata = data; return 0; } @@ -1566,6 +1623,12 @@ Cocoa_SetWindowFullscreenSpace(SDL_Window * window, SDL_bool state) return succeeded; } +int +Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled) +{ + 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/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 6c0b9b279..7ff47d4c2 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -30,6 +30,7 @@ #include "../../events/SDL_events_c.h" #include "../../events/SDL_touch_c.h" #include "../../events/scancodes_windows.h" +#include "SDL_assert.h" /* Dropfile support */ #include @@ -438,33 +439,55 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) HRAWINPUT hRawInput = (HRAWINPUT)lParam; RAWINPUT inp; UINT size = sizeof(inp); + const SDL_bool isRelative = mouse->relative_mode || mouse->relative_mode_warp; + const SDL_bool isCapture = ((data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0); - if (!mouse->relative_mode || mouse->relative_mode_warp || mouse->focus != data->window) { - break; + if (!isRelative || mouse->focus != data->window) { + if (!isCapture) { + break; + } } GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER)); /* Mouse data */ if (inp.header.dwType == RIM_TYPEMOUSE) { - RAWMOUSE* mouse = &inp.data.mouse; + if (isRelative) { + RAWMOUSE* mouse = &inp.data.mouse; + + if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) { + SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY); + } else { + /* synthesize relative moves from the abs position */ + static SDL_Point initialMousePoint; + if (initialMousePoint.x == 0 && initialMousePoint.y == 0) { + initialMousePoint.x = mouse->lLastX; + initialMousePoint.y = mouse->lLastY; + } + + SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y) ); - if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) { - SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY); - } else { - /* synthesize relative moves from the abs position */ - static SDL_Point initialMousePoint; - if (initialMousePoint.x == 0 && initialMousePoint.y == 0) { initialMousePoint.x = mouse->lLastX; initialMousePoint.y = mouse->lLastY; } - - SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y)); - - initialMousePoint.x = mouse->lLastX; - initialMousePoint.y = mouse->lLastY; + WIN_CheckRawMouseButtons( mouse->usButtonFlags, data ); + } else if (isCapture) { + /* we check for where Windows thinks the system cursor lives in this case, so we don't really lose mouse accel, etc. */ + POINT pt; + HWND hwnd = data->hwnd; + GetCursorPos(&pt); + if (WindowFromPoint(pt) != hwnd) { /* if in the window, WM_MOUSEMOVE, etc, will cover it. */ + ScreenToClient(data->hwnd, &pt); + SDL_SendMouseMotion(data->window, 0, 0, (int) pt.x, (int) pt.y); + SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_LEFT); + SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_RIGHT); + SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE); + SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1); + SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2); + } + } else { + SDL_assert(0 && "Shouldn't happen"); } - WIN_CheckRawMouseButtons(mouse->usButtonFlags, data); } } break; @@ -509,7 +532,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) #ifdef WM_MOUSELEAVE case WM_MOUSELEAVE: - if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode) { + if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { if (!IsIconic(hwnd)) { POINT cursorPos; GetCursorPos(&cursorPos); @@ -863,6 +886,32 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) return 0; } break; + + case WM_NCHITTEST: + { + SDL_Window *window = data->window; + if (window->hit_test) { + POINT winpoint = { (int) LOWORD(lParam), (int) HIWORD(lParam) }; + if (ScreenToClient(data->hwnd, &winpoint)) { + const SDL_Point point = { (int) winpoint.x, (int) winpoint.y }; + const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + switch (rc) { + case SDL_HITTEST_DRAGGABLE: return HTCAPTION; + case SDL_HITTEST_RESIZE_TOPLEFT: return HTTOPLEFT; + case SDL_HITTEST_RESIZE_TOP: return HTTOP; + case SDL_HITTEST_RESIZE_TOPRIGHT: return HTTOPRIGHT; + case SDL_HITTEST_RESIZE_RIGHT: return HTRIGHT; + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: return HTBOTTOMRIGHT; + case SDL_HITTEST_RESIZE_BOTTOM: return HTBOTTOM; + case SDL_HITTEST_RESIZE_BOTTOMLEFT: return HTBOTTOMLEFT; + case SDL_HITTEST_RESIZE_LEFT: return HTLEFT; + } + } + /* If we didn't return, this will call DefWindowProc below. */ + } + } + break; + } /* If there's a window proc, assume it's going to handle messages */ diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c index 29665906e..50a9dacb3 100644 --- a/src/video/windows/SDL_windowsmouse.c +++ b/src/video/windows/SDL_windowsmouse.c @@ -30,6 +30,44 @@ HCURSOR SDL_cursor = NULL; +static int rawInputEnableCount = 0; + +static int +ToggleRawInput(SDL_bool enabled) +{ + RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */ + + if (enabled) { + rawInputEnableCount++; + if (rawInputEnableCount > 1) { + return 0; /* already done. */ + } + } else { + if (rawInputEnableCount == 0) { + return 0; /* already done. */ + } + rawInputEnableCount--; + if (rawInputEnableCount > 0) { + return 0; /* not time to disable yet */ + } + } + + if (!enabled) { + rawMouse.dwFlags |= RIDEV_REMOVE; + } + + /* (Un)register raw input for mice */ + if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) { + + /* Only return an error when registering. If we unregister and fail, + then it's probably that we unregistered twice. That's OK. */ + if (enabled) { + return SDL_Unsupported(); + } + } + return 0; +} + static SDL_Cursor * WIN_CreateDefaultCursor() @@ -211,22 +249,43 @@ WIN_WarpMouseGlobal(int x, int y) static int WIN_SetRelativeMouseMode(SDL_bool enabled) { - RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */ + return ToggleRawInput(enabled); +} - if (!enabled) { - rawMouse.dwFlags |= RIDEV_REMOVE; - } - - /* (Un)register raw input for mice */ - if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) { - - /* Only return an error when registering. If we unregister and fail, - then it's probably that we unregistered twice. That's OK. */ - if (enabled) { - return SDL_Unsupported(); +static int +WIN_CaptureMouse(SDL_Window *window) +{ + if (!window) { + SDL_Window *focusWin = SDL_GetKeyboardFocus(); + if (focusWin) { + SDL_WindowData *data = (SDL_WindowData *)focusWin->driverdata; + WIN_OnWindowEnter(SDL_GetVideoDevice(), focusWin); /* make sure WM_MOUSELEAVE messages are (re)enabled. */ } } - return 0; + + /* While we were thinking of SetCapture() when designing this API in SDL, + we didn't count on the fact that SetCapture() only tracks while the + left mouse button is held down! Instead, we listen for raw mouse input + and manually query the mouse when it leaves the window. :/ */ + return ToggleRawInput(window != NULL); +} + +static Uint32 +WIN_GetGlobalMouseState(int *x, int *y) +{ + Uint32 retval = 0; + POINT pt = { 0, 0 }; + GetCursorPos(&pt); + *x = (int) pt.x; + *y = (int) pt.y; + + retval |= GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0; + retval |= GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0; + retval |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0; + retval |= GetAsyncKeyState(VK_X1BUTTON) & 0x8000 ? SDL_BUTTON_X1MASK : 0; + retval |= GetAsyncKeyState(VK_X2BUTTON) & 0x8000 ? SDL_BUTTON_X2MASK : 0; + + return retval; } void @@ -241,6 +300,8 @@ WIN_InitMouse(_THIS) mouse->WarpMouse = WIN_WarpMouse; mouse->WarpMouseGlobal = WIN_WarpMouseGlobal; mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode; + mouse->CaptureMouse = WIN_CaptureMouse; + mouse->GetGlobalMouseState = WIN_GetGlobalMouseState; SDL_SetDefaultCursor(WIN_CreateDefaultCursor()); @@ -256,6 +317,11 @@ WIN_QuitMouse(_THIS) mouse->def_cursor = NULL; mouse->cur_cursor = NULL; } + + if (rawInputEnableCount) { /* force RAWINPUT off here. */ + rawInputEnableCount = 1; + ToggleRawInput(SDL_FALSE); + } } #endif /* SDL_VIDEO_DRIVER_WINDOWS */ diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index bb64d9147..d87b9cff9 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -144,6 +144,7 @@ WIN_CreateDevice(int devindex) device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer; device->OnWindowEnter = WIN_OnWindowEnter; + device->SetWindowHitTest = WIN_SetWindowHitTest; device->shape_driver.CreateShaper = Win32_CreateShaper; device->shape_driver.SetWindowShape = Win32_SetWindowShape; diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 15ae1a114..dfc997b6b 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -785,6 +785,12 @@ WIN_UpdateClipCursor(SDL_Window *window) } } +int +WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled) +{ + return 0; /* just succeed, the real work is done elsewhere. */ +} + #endif /* SDL_VIDEO_DRIVER_WINDOWS */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index a9fa65d14..25d108bd5 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -69,6 +69,7 @@ extern SDL_bool WIN_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info); extern void WIN_OnWindowEnter(_THIS, SDL_Window * window); extern void WIN_UpdateClipCursor(SDL_Window *window); +extern int WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); #endif /* _SDL_windowswindow_h */ diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 302b64b87..5b1f74ae6 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -40,6 +40,42 @@ #include +#ifndef _NET_WM_MOVERESIZE_SIZE_TOPLEFT +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_TOP +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_TOPRIGHT +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_RIGHT +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOM +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#endif + +#ifndef _NET_WM_MOVERESIZE_SIZE_LEFT +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#endif + +#ifndef _NET_WM_MOVERESIZE_MOVE +#define _NET_WM_MOVERESIZE_MOVE 8 +#endif + typedef struct { unsigned char *data; int format, count; @@ -347,6 +383,124 @@ X11_DispatchUnmapNotify(SDL_WindowData *data) SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); } +static void +InitiateWindowMove(_THIS, const SDL_WindowData *data, const SDL_Point *point) +{ + SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; + SDL_Window* window = data->window; + Display *display = viddata->display; + + /* !!! FIXME: we need to regrab this if necessary when the drag is done. */ + X11_XUngrabPointer(display, 0L); + X11_XFlush(display); + + XEvent evt; + evt.xclient.type = ClientMessage; + evt.xclient.window = data->xwindow; + evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True); + evt.xclient.format = 32; + evt.xclient.data.l[0] = window->x + point->x; + evt.xclient.data.l[1] = window->y + point->y; + evt.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + evt.xclient.data.l[3] = Button1; + evt.xclient.data.l[4] = 0; + X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); + + X11_XSync(display, 0); +} + +static void +InitiateWindowResize(_THIS, const SDL_WindowData *data, const SDL_Point *point, int direction) +{ + SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata; + SDL_Window* window = data->window; + Display *display = viddata->display; + + if (direction < _NET_WM_MOVERESIZE_SIZE_TOPLEFT || direction > _NET_WM_MOVERESIZE_SIZE_LEFT) + return; + + /* !!! FIXME: we need to regrab this if necessary when the drag is done. */ + X11_XUngrabPointer(display, 0L); + X11_XFlush(display); + + XEvent evt; + evt.xclient.type = ClientMessage; + evt.xclient.window = data->xwindow; + evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True); + evt.xclient.format = 32; + evt.xclient.data.l[0] = window->x + point->x; + evt.xclient.data.l[1] = window->y + point->y; + evt.xclient.data.l[2] = direction; + evt.xclient.data.l[3] = Button1; + evt.xclient.data.l[4] = 0; + X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt); + + X11_XSync(display, 0); +} + +static SDL_bool +ProcessHitTest(_THIS, const SDL_WindowData *data, const XEvent *xev) +{ + SDL_Window *window = data->window; + SDL_bool ret = SDL_FALSE; + + if (window->hit_test) { + const SDL_Point point = { xev->xbutton.x, xev->xbutton.y }; + const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); + switch (rc) { + case SDL_HITTEST_DRAGGABLE: { + InitiateWindowMove(_this, data, &point); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_TOPLEFT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOPLEFT); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_TOP: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOP); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_TOPRIGHT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOPRIGHT); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_RIGHT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_RIGHT); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_BOTTOM: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOM); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_BOTTOMLEFT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT); + ret = SDL_TRUE; + } + break; + case SDL_HITTEST_RESIZE_LEFT: { + InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_LEFT); + ret = SDL_TRUE; + } + break; + default: + break; + } + } + + return ret; +} + static void X11_DispatchEvent(_THIS) { @@ -820,6 +974,11 @@ X11_DispatchEvent(_THIS) if (X11_IsWheelEvent(display,&xevent,&ticks)) { SDL_SendMouseWheel(data->window, 0, 0, ticks); } else { + if(xevent.xbutton.button == Button1) { + if (ProcessHitTest(_this, data, &xevent)) { + break; /* don't pass this event on to app. */ + } + } SDL_SendMouseButton(data->window, 0, SDL_PRESSED, xevent.xbutton.button); } } diff --git a/src/video/x11/SDL_x11mouse.c b/src/video/x11/SDL_x11mouse.c index 35baa5cee..02d96da77 100644 --- a/src/video/x11/SDL_x11mouse.c +++ b/src/video/x11/SDL_x11mouse.c @@ -339,6 +339,62 @@ X11_SetRelativeMouseMode(SDL_bool enabled) return -1; } +static int +X11_CaptureMouse(SDL_Window *window) +{ + Display *display = GetDisplay(); + + if (window) { + SDL_WindowData *data = (SDL_WindowData *) window->driverdata; + const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + const int rc = X11_XGrabPointer(display, data->xwindow, False, + mask, GrabModeAsync, GrabModeAsync, + None, None, CurrentTime); + if (rc != GrabSuccess) { + return SDL_SetError("X server refused mouse capture"); + } + } else { + X11_XUngrabPointer(display, CurrentTime); + } + + X11_XSync(display, False); + + return 0; +} + +static Uint32 +X11_GetGlobalMouseState(int *x, int *y) +{ + Display *display = GetDisplay(); + const int num_screens = SDL_GetNumVideoDisplays(); + int i; + + /* !!! FIXME: should we XSync() here first? */ + + for (i = 0; i < num_screens; i++) { + SDL_DisplayData *data = (SDL_DisplayData *) SDL_GetDisplayDriverData(i); + if (data != NULL) { + Window root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + if (X11_XQueryPointer(display, RootWindow(display, data->screen), &root, &child, &rootx, &rooty, &winx, &winy, &mask)) { + Uint32 retval = 0; + retval |= (mask & Button1Mask) ? SDL_BUTTON_LMASK : 0; + retval |= (mask & Button2Mask) ? SDL_BUTTON_MMASK : 0; + retval |= (mask & Button3Mask) ? SDL_BUTTON_RMASK : 0; + *x = data->x + rootx; + *y = data->y + rooty; + return retval; + } + } + } + + SDL_assert(0 && "The pointer wasn't on any X11 screen?!"); + + return 0; +} + + void X11_InitMouse(_THIS) { @@ -351,6 +407,8 @@ X11_InitMouse(_THIS) mouse->WarpMouse = X11_WarpMouse; mouse->WarpMouseGlobal = X11_WarpMouseGlobal; mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode; + mouse->CaptureMouse = X11_CaptureMouse; + mouse->GetGlobalMouseState = X11_GetGlobalMouseState; SDL_SetDefaultCursor(X11_CreateDefaultCursor()); } diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index d77fb7fe3..4ede655e3 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -243,6 +243,7 @@ X11_CreateDevice(int devindex) device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer; device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer; device->GetWindowWMInfo = X11_GetWindowWMInfo; + 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 2eccd279b..d2d02dd86 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -1445,6 +1445,12 @@ X11_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info) } } +int +X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled) +{ + return 0; /* just succeed, the real work is done elsewhere. */ +} + #endif /* SDL_VIDEO_DRIVER_X11 */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index cf0d7f799..789d2f7cc 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -93,6 +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_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); #endif /* _SDL_x11window_h */ diff --git a/test/Makefile.in b/test/Makefile.in index 7ed1a7ac9..227bd0eb0 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -23,6 +23,7 @@ TARGETS = \ testgles$(EXE) \ testgles2$(EXE) \ testhaptic$(EXE) \ + testhittesting$(EXE) \ testrumble$(EXE) \ testhotplug$(EXE) \ testthread$(EXE) \ @@ -108,6 +109,9 @@ testintersections$(EXE): $(srcdir)/testintersections.c testrelative$(EXE): $(srcdir)/testrelative.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) +testhittesting$(EXE): $(srcdir)/testhittesting.c + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + testdraw2$(EXE): $(srcdir)/testdraw2.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) diff --git a/test/testhittesting.c b/test/testhittesting.c new file mode 100644 index 000000000..5e32be42d --- /dev/null +++ b/test/testhittesting.c @@ -0,0 +1,134 @@ +#include +#include "SDL.h" + +/* !!! FIXME: rewrite this to be wired in to test framework. */ + +#define RESIZE_BORDER 20 + +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; + int w, h; + + for (i = 0; i < numareas; i++) { + if (SDL_PointInRect(pt, &areas[i])) { + SDL_Log("HIT-TEST: DRAGGABLE\n"); + return SDL_HITTEST_DRAGGABLE; + } + } + + SDL_GetWindowSize(window, &w, &h); + + #define REPORT_RESIZE_HIT(name) { \ + SDL_Log("HIT-TEST: RESIZE_" #name "\n"); \ + return SDL_HITTEST_RESIZE_##name; \ + } + + if (pt->x < RESIZE_BORDER && pt->y < RESIZE_BORDER) { + REPORT_RESIZE_HIT(TOPLEFT); + } else if (pt->x > RESIZE_BORDER && pt->x < w - RESIZE_BORDER && pt->y < RESIZE_BORDER) { + REPORT_RESIZE_HIT(TOP); + } else if (pt->x > w - RESIZE_BORDER && pt->y < RESIZE_BORDER) { + REPORT_RESIZE_HIT(TOPRIGHT); + } else if (pt->x > w - RESIZE_BORDER && pt->y > RESIZE_BORDER && pt->y < h - RESIZE_BORDER) { + REPORT_RESIZE_HIT(RIGHT); + } else if (pt->x > w - RESIZE_BORDER && pt->y > h - RESIZE_BORDER) { + REPORT_RESIZE_HIT(BOTTOMRIGHT); + } else if (pt->x < w - RESIZE_BORDER && pt->x > RESIZE_BORDER && pt->y > h - RESIZE_BORDER) { + REPORT_RESIZE_HIT(BOTTOM); + } else if (pt->x < RESIZE_BORDER && pt->y > h - RESIZE_BORDER) { + REPORT_RESIZE_HIT(BOTTOMLEFT); + } else if (pt->x < RESIZE_BORDER && pt->y < h - RESIZE_BORDER && pt->y > RESIZE_BORDER) { + REPORT_RESIZE_HIT(LEFT); + } + + SDL_Log("HIT-TEST: NORMAL\n"); + return SDL_HITTEST_NORMAL; +} + + +int main(int argc, char **argv) +{ + int done = 0; + SDL_Window *window; + SDL_Renderer *renderer; + + /* !!! 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 | SDL_WINDOW_RESIZABLE); + renderer = SDL_CreateRenderer(window, -1, 0); + + if (SDL_SetWindowHitTest(window, hitTest, NULL) == -1) { + SDL_Log("Enabling hit-testing failed!\n"); + SDL_Quit(); + return 1; + } + + while (!done) + { + SDL_Event e; + int nothing_to_do = 1; + + 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); + + while (SDL_PollEvent(&e)) { + nothing_to_do = 0; + + switch (e.type) + { + case SDL_MOUSEBUTTONDOWN: + SDL_Log("button down!\n"); + break; + + case SDL_MOUSEBUTTONUP: + SDL_Log("button up!\n"); + break; + + case SDL_WINDOWEVENT: + if (e.window.event == SDL_WINDOWEVENT_MOVED) { + SDL_Log("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; + } + } + break; + + case SDL_QUIT: + done = 1; + break; + } + } + + if (nothing_to_do) { + SDL_Delay(50); + } + } + + SDL_Quit(); + return 0; +}