Added OS X implementation of sf::Window::setCursorGrabbed

This implementation uses the following workaround:
 - resize flag is removed from the window when the cursor is grabbed
 - when grabbed, the cursor is projected inside the view if needed
 - to avoid letting the user clic outside the view, the cursor is
   disconnected and manually moved using relative motion
 - the initial potential projection of the cursor results in a big
   delta for the next move and needed to be somehow ignored (see
   note about m_deltaXBuffer and m_deltaYBuffer in SFOpenGLView.h)
This commit is contained in:
Marco Antognini 2014-05-18 00:22:20 +02:00 committed by Lukas Dürrenberger
parent 90c01d3030
commit 427ce77d4e
10 changed files with 263 additions and 26 deletions

View File

@ -27,6 +27,7 @@
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Window/OSX/WindowImplCocoa.hpp> #include <SFML/Window/OSX/WindowImplCocoa.hpp>
#include <cmath>
#import <SFML/Window/OSX/SFOpenGLView.h> #import <SFML/Window/OSX/SFOpenGLView.h>
#import <SFML/Window/OSX/SFOpenGLView+mouse_priv.h> #import <SFML/Window/OSX/SFOpenGLView+mouse_priv.h>
@ -52,20 +53,29 @@
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(void)updateMouseState -(void)updateMouseState
{ {
// Update in/out state
BOOL mouseWasIn = m_mouseIsIn; BOOL mouseWasIn = m_mouseIsIn;
m_mouseIsIn = [self isMouseInside]; m_mouseIsIn = [self isMouseInside];
if (m_requester == 0)
return;
// Send event if needed. // Send event if needed.
if (m_requester != 0)
{
if (mouseWasIn && !m_mouseIsIn) if (mouseWasIn && !m_mouseIsIn)
m_requester->mouseMovedOut(); m_requester->mouseMovedOut();
else if (!mouseWasIn && m_mouseIsIn) else if (!mouseWasIn && m_mouseIsIn)
m_requester->mouseMovedIn(); m_requester->mouseMovedIn();
} }
}
////////////////////////////////////////////////////////
-(void)setCursorGrabbed:(BOOL)grabbed
{
m_cursorGrabbed = grabbed;
[self updateCursorGrabbed];
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(void)mouseDown:(NSEvent*)theEvent -(void)mouseDown:(NSEvent*)theEvent
@ -199,18 +209,69 @@
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(void)handleMouseMove:(NSEvent*)theEvent -(void)handleMouseMove:(NSEvent*)theEvent
{
if (m_requester != 0)
{ {
NSPoint loc = [self cursorPositionFromEvent:theEvent]; NSPoint loc = [self cursorPositionFromEvent:theEvent];
// If the cursor is grabbed, cursorPositionFromEvent: will
// return its correct position but not move actually it
// so we do it now.
if ([self isCursorCurrentlyGrabbed])
[self moveCursorTo:loc];
// Make sure the point is inside the view. // Make sure the point is inside the view.
// (mouseEntered: and mouseExited: are not immediately called // (mouseEntered: and mouseExited: are not immediately called
// when the mouse is dragged. That would be too easy!) // when the mouse is dragged. That would be too easy!)
[self updateMouseState]; [self updateMouseState];
if (m_mouseIsIn) if ((m_requester != 0) && m_mouseIsIn)
m_requester->mouseMovedAt(loc.x, loc.y); m_requester->mouseMovedAt(loc.x, loc.y);
} }
////////////////////////////////////////////////////////
-(BOOL)isCursorCurrentlyGrabbed
{
return [[self window] isKeyWindow] && (m_cursorGrabbed || m_fullscreen);
}
////////////////////////////////////////////////////////
-(void)updateCursorGrabbed
{
// Disable/enable normal movements of the cursor
// and project the cursor if needed.
if ([self isCursorCurrentlyGrabbed])
{
CGAssociateMouseAndMouseCursorPosition(NO);
// Similarly to handleMouseMove: but without event.
NSPoint loc = [self cursorPositionFromEvent:nil];
[self moveCursorTo:loc];
}
else
{
CGAssociateMouseAndMouseCursorPosition(YES);
}
}
////////////////////////////////////////////////////////
-(void)moveCursorTo:(NSPoint)loc
{
// Convert the point from SFML coord system to screen coord system.
NSPoint screenLocation = [self computeGlobalPositionOfRelativePoint:loc];
// This won't produce a move event, which is perfect if the cursor was grabbed
// as we move it manually based on delta values of the cursor.
CGDisplayMoveCursorToPoint([self displayId], NSPointToCGPoint(screenLocation));
}
////////////////////////////////////////////////////////
-(CGDirectDisplayID)displayId
{
NSScreen* screen = [[self window] screen];
NSNumber* displayId = [[screen deviceDescription] objectForKey:@"NSScreenNumber"];
return [displayId intValue];
} }
@ -247,17 +308,73 @@
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil -(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil
{ {
NSPoint loc; NSPoint rawPos;
// If no event given then get current mouse pos. // If no event given then get current mouse pos.
if (eventOrNil == nil) if (eventOrNil == nil)
{ rawPos = [[self window] mouseLocationOutsideOfEventStream];
NSPoint rawPos = [[self window] mouseLocationOutsideOfEventStream];
loc = [self convertPoint:rawPos fromView:nil];
}
else else
rawPos = [eventOrNil locationInWindow];
if ([self isCursorCurrentlyGrabbed])
{ {
loc = [self convertPoint:[eventOrNil locationInWindow] fromView:nil]; if (eventOrNil != nil)
{
// Special case when the mouse is grabbed:
// we need to take into account the delta since the cursor
// is dissociated from its position.
// Ignore any non-move related event
if (([eventOrNil type] == NSMouseMoved) ||
([eventOrNil type] == NSLeftMouseDragged) ||
([eventOrNil type] == NSRightMouseDragged) ||
([eventOrNil type] == NSOtherMouseDragged))
{
// Without this factor, the cursor flies around waaay too fast!
// But I don't know if it because of retina display or because
// some event are sent twice (and that in itself is another mystery).
CGFloat factor = 2;
// Also, this factor is not the same when keeping track of how much
// we move the cursor (buffers) when projecting the cursor into the
// view when grabbing the cursor for the first time.
CGFloat factorBuffer = m_fullscreen ? 1 : 2;
CGFloat deltaX = [eventOrNil deltaX];
CGFloat deltaY = [eventOrNil deltaY];
// If the buffer for X is empty, move the cursor;
// otherwise decrement this buffer a bit.
if (m_deltaXBuffer <= 0)
rawPos.x += deltaX / factor;
else
m_deltaXBuffer -= std::abs(deltaX / factorBuffer);
// Rinse and repeat for Y.
if (m_deltaYBuffer <= 0)
rawPos.y -= deltaY / factor;
else
m_deltaYBuffer -= std::abs(deltaY / factorBuffer);
} }
}
// We also make sure the new point is inside the view
NSSize size = [self frame].size;
NSPoint origin = [self frame].origin;
NSPoint oldPos = rawPos;
rawPos.x = std::min(std::max(origin.x, rawPos.x), origin.x + size.width - 1);
rawPos.y = std::min(std::max(origin.y + 1, rawPos.y), origin.y + size.height);
// Note: the `-1` and `+1` on the two lines above prevent the user to click
// on the left or below the window, repectively, and therefore prevent the
// application to lose focus by accident. The sign of this offset is determinded
// by the direction of the x and y axis.
// Increase X and Y buffer with the distance of the projection
m_deltaXBuffer += std::abs(rawPos.x - oldPos.x);
m_deltaYBuffer += std::abs(rawPos.y - oldPos.y);
}
NSPoint loc = [self convertPoint:rawPos fromView:nil];
// Don't forget to change to SFML coord system. // Don't forget to change to SFML coord system.
float h = [self frame].size.height; float h = [self frame].size.height;

View File

@ -66,6 +66,37 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(void)handleMouseMove:(NSEvent*)theEvent; -(void)handleMouseMove:(NSEvent*)theEvent;
////////////////////////////////////////////////////////////
/// \brief Check whether the cursor is grabbed or not
///
/// The cursor is grabbed if the window is active (key) and
/// either it is in fullscreen mode or the user wants to
/// grab it.
///
////////////////////////////////////////////////////////////
-(BOOL)isCursorCurrentlyGrabbed;
////////////////////////////////////////////////////////////
/// \brief (Dis)connect the cursor's movements from/to the system
/// and project the cursor into the view
///
////////////////////////////////////////////////////////////
-(void)updateCursorGrabbed;
////////////////////////////////////////////////////////////
/// \brief Move the cursor to the given location
///
/// \param loc location expressed in SFML coordinate system
///
////////////////////////////////////////////////////////////
-(void)moveCursorTo:(NSPoint)loc;
////////////////////////////////////////////////////////////
/// \brief Get the display identifier on which the view is
///
////////////////////////////////////////////////////////////
-(CGDirectDisplayID)displayId;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Convert the NSEvent mouse button type to SFML type /// \brief Convert the NSEvent mouse button type to SFML type
/// ///

View File

@ -52,6 +52,19 @@ namespace sf {
/// implementation. However, all attributes are defined in the main /// implementation. However, all attributes are defined in the main
/// interface declaration right below. /// interface declaration right below.
/// ///
/// Note about deltaXBuffer and deltaYBuffer: when grabbing the cursor
/// for the first time, either by entering fullscreen or through
/// setCursorGrabbed:, the cursor might be projected into the view.
/// Doing this will result in a big delta (relative movement) in the
/// next move event (cursorPositionFromEvent:), because no move event
/// is generated, which in turn will give the impression that the user
/// want to move the cursor by the same distance it was projected. To
/// prevent the cursor to fly twice the distance we keep track of how
/// much the cursor was projected in deltaXBuffer and deltaYBuffer. In
/// cursorPositionFromEvent: we can then reduce/augment those buffers
/// to determine when a move event should result in an actual move of
/// the cursor (that was disconnected from the system).
///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@interface SFOpenGLView : NSOpenGLView @interface SFOpenGLView : NSOpenGLView
{ {
@ -61,6 +74,9 @@ namespace sf {
NSTrackingArea* m_trackingArea; ///< Mouse tracking area NSTrackingArea* m_trackingArea; ///< Mouse tracking area
BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not
CGFloat m_scaleFactor; ///< Display scale factor (e.g. 1x for classic display, 2x for retina) CGFloat m_scaleFactor; ///< Display scale factor (e.g. 1x for classic display, 2x for retina)
BOOL m_cursorGrabbed; ///< Is the mouse cursor trapped?
CGFloat m_deltaXBuffer; ///< See note about cursor grabbing above
CGFloat m_deltaYBuffer; ///< See note about cursor grabbing above
// Hidden text view used to convert key event to actual chars. // Hidden text view used to convert key event to actual chars.
// We use a silent responder to prevent sound alerts. // We use a silent responder to prevent sound alerts.
@ -143,6 +159,8 @@ namespace sf {
/// ///
/// \param eventOrNil if nil the cursor position is the current one /// \param eventOrNil if nil the cursor position is the current one
/// ///
/// \return the mouse position in SFML coord system
///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil; -(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil;
@ -154,4 +172,22 @@ namespace sf {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(BOOL)isMouseInside; -(BOOL)isMouseInside;
////////////////////////////////////////////////////////////
/// Clips or releases the mouse cursor
///
/// Generate a MouseEntered event when it makes sense.
///
/// \param grabbed YES to grab, NO to release
///
////////////////////////////////////////////////////////////
-(void)setCursorGrabbed:(BOOL)grabbed;
////////////////////////////////////////////////////////////
/// Update the cursor position according to the grabbing behaviour
///
/// This function has to be called when the window's state change
///
////////////////////////////////////////////////////////////
-(void)updateCursorGrabbed;
@end @end

View File

@ -108,6 +108,9 @@
m_fullscreen = isFullscreen; m_fullscreen = isFullscreen;
m_scaleFactor = 1.0; // Default value; it will be updated in finishInit m_scaleFactor = 1.0; // Default value; it will be updated in finishInit
m_cursorGrabbed = NO;
m_deltaXBuffer = 0;
m_deltaYBuffer = 0;
// Create a hidden text view for parsing key down event properly // Create a hidden text view for parsing key down event properly
m_silentResponder = [[SFSilentResponder alloc] init]; m_silentResponder = [[SFSilentResponder alloc] init];
@ -163,8 +166,9 @@
name:NSWindowDidChangeScreenProfileNotification name:NSWindowDidChangeScreenProfileNotification
object:[self window]]; object:[self window]];
// Now that we have a window, set up correctly the scale factor // Now that we have a window, set up correctly the scale factor and cursor grabbing
[self updateScaleFactor]; [self updateScaleFactor];
[self updateCursorGrabbed]; // update for fullscreen
} }
@ -245,6 +249,7 @@
// Update mouse internal state. // Update mouse internal state.
[self updateMouseState]; [self updateMouseState];
[self updateCursorGrabbed];
// Update the OGL view to fit the new size. // Update the OGL view to fit the new size.
[self update]; [self update];
@ -263,6 +268,8 @@
{ {
(void)notification; (void)notification;
[self updateCursorGrabbed];
if (m_requester) if (m_requester)
m_requester->windowGainedFocus(); m_requester->windowGainedFocus();
@ -276,6 +283,8 @@
{ {
(void)notification; (void)notification;
[self updateCursorGrabbed];
if (m_requester) if (m_requester)
m_requester->windowLostFocus(); m_requester->windowLostFocus();

View File

@ -121,6 +121,13 @@
} }
////////////////////////////////////////////////////////
-(void)setCursorGrabbed:(BOOL)grabbed
{
[m_oglView setCursorGrabbed:grabbed];
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(NSPoint)position -(NSPoint)position
{ {

View File

@ -49,6 +49,11 @@ namespace sf {
/// Used when SFML handle everything and when a NSWindow* is given /// Used when SFML handle everything and when a NSWindow* is given
/// as handle to WindowImpl. /// as handle to WindowImpl.
/// ///
/// When grabbing the cursor, if the window is resizeable, m_restoreResize is
/// set to YES and the window is marked as not resizeable. This is to prevent
/// accidental resize by the user. When the cursor is released, the window
/// style is restored.
///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@interface SFWindowController : NSResponder <WindowImplDelegateProtocol, NSWindowDelegate> @interface SFWindowController : NSResponder <WindowImplDelegateProtocol, NSWindowDelegate>
{ {
@ -56,6 +61,7 @@ namespace sf {
SFOpenGLView* m_oglView; ///< OpenGL view for rendering SFOpenGLView* m_oglView; ///< OpenGL view for rendering
sf::priv::WindowImplCocoa* m_requester; ///< Requester sf::priv::WindowImplCocoa* m_requester; ///< Requester
BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not
BOOL m_restoreResize; ///< See note above
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -97,6 +97,8 @@
m_window = nil; m_window = nil;
m_oglView = nil; m_oglView = nil;
m_requester = 0; m_requester = 0;
m_fullscreen = NO; // assuming this is the case... too hard to handle anyway.
m_restoreResize = NO;
// Retain the window for our own use. // Retain the window for our own use.
m_window = [window retain]; m_window = [window retain];
@ -148,6 +150,7 @@
m_oglView = nil; m_oglView = nil;
m_requester = 0; m_requester = 0;
m_fullscreen = (style & sf::Style::Fullscreen); m_fullscreen = (style & sf::Style::Fullscreen);
m_restoreResize = NO;
if (m_fullscreen) if (m_fullscreen)
[self setupFullscreenViewWithMode:mode]; [self setupFullscreenViewWithMode:mode];
@ -345,6 +348,29 @@
} }
////////////////////////////////////////////////////////
-(void)setCursorGrabbed:(BOOL)grabbed
{
// Remove or restore resizeable style if needed
BOOL resizeable = [m_window styleMask] & NSResizableWindowMask;
if (grabbed && resizeable)
{
m_restoreResize = YES;
NSUInteger newStyle = [m_window styleMask] & ~NSResizableWindowMask;
[m_window setStyleMask:newStyle];
}
else if (!grabbed && m_restoreResize)
{
m_restoreResize = NO;
NSUInteger newStyle = [m_window styleMask] | NSResizableWindowMask;
[m_window setStyleMask:newStyle];
}
// Forward to our view
[m_oglView setCursorGrabbed:grabbed];
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(NSPoint)position -(NSPoint)position
{ {
@ -379,6 +405,9 @@
// Place the window. // Place the window.
[m_window setFrameTopLeftPoint:point]; [m_window setFrameTopLeftPoint:point];
// In case the cursor was grabbed we need to update its position
[m_oglView updateCursorGrabbed];
} }

View File

@ -316,7 +316,7 @@ public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Grab or release the mouse cursor /// \brief Grab or release the mouse cursor
/// ///
/// \param grabbed True to enable, false to disable /// \param grabbed True to grab, false to release
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
virtual void setMouseCursorGrabbed(bool grabbed); virtual void setMouseCursorGrabbed(bool grabbed);

View File

@ -489,7 +489,7 @@ void WindowImplCocoa::setMouseCursorVisible(bool visible)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void WindowImplCocoa::setMouseCursorGrabbed(bool grabbed) void WindowImplCocoa::setMouseCursorGrabbed(bool grabbed)
{ {
[m_delegate setCursorGrabbed:grabbed];
} }

View File

@ -99,8 +99,10 @@ namespace sf {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Grab or release the mouse cursor /// \brief Grab or release the mouse cursor
/// ///
/// \param grabbed YES to grab, NO to release
///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(void)setCursorGrabbed:(bool)grabbed; -(void)setCursorGrabbed:(BOOL)grabbed;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get window position /// \brief Get window position