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:
parent
90c01d3030
commit
427ce77d4e
@ -27,6 +27,7 @@
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Window/OSX/WindowImplCocoa.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#import <SFML/Window/OSX/SFOpenGLView.h>
|
||||
#import <SFML/Window/OSX/SFOpenGLView+mouse_priv.h>
|
||||
@ -52,20 +53,29 @@
|
||||
////////////////////////////////////////////////////////
|
||||
-(void)updateMouseState
|
||||
{
|
||||
// Update in/out state
|
||||
BOOL mouseWasIn = m_mouseIsIn;
|
||||
m_mouseIsIn = [self isMouseInside];
|
||||
|
||||
if (m_requester == 0)
|
||||
return;
|
||||
|
||||
// Send event if needed.
|
||||
if (mouseWasIn && !m_mouseIsIn)
|
||||
m_requester->mouseMovedOut();
|
||||
else if (!mouseWasIn && m_mouseIsIn)
|
||||
m_requester->mouseMovedIn();
|
||||
if (m_requester != 0)
|
||||
{
|
||||
if (mouseWasIn && !m_mouseIsIn)
|
||||
m_requester->mouseMovedOut();
|
||||
else if (!mouseWasIn && m_mouseIsIn)
|
||||
m_requester->mouseMovedIn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
-(void)setCursorGrabbed:(BOOL)grabbed
|
||||
{
|
||||
m_cursorGrabbed = grabbed;
|
||||
|
||||
[self updateCursorGrabbed];
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
-(void)mouseDown:(NSEvent*)theEvent
|
||||
@ -200,17 +210,68 @@
|
||||
////////////////////////////////////////////////////////
|
||||
-(void)handleMouseMove:(NSEvent*)theEvent
|
||||
{
|
||||
if (m_requester != 0)
|
||||
{
|
||||
NSPoint loc = [self cursorPositionFromEvent:theEvent];
|
||||
NSPoint loc = [self cursorPositionFromEvent:theEvent];
|
||||
|
||||
// Make sure the point is inside the view.
|
||||
// (mouseEntered: and mouseExited: are not immediately called
|
||||
// when the mouse is dragged. That would be too easy!)
|
||||
[self updateMouseState];
|
||||
if (m_mouseIsIn)
|
||||
m_requester->mouseMovedAt(loc.x, loc.y);
|
||||
// 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.
|
||||
// (mouseEntered: and mouseExited: are not immediately called
|
||||
// when the mouse is dragged. That would be too easy!)
|
||||
[self updateMouseState];
|
||||
if ((m_requester != 0) && m_mouseIsIn)
|
||||
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,18 +308,74 @@
|
||||
////////////////////////////////////////////////////////
|
||||
-(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil
|
||||
{
|
||||
NSPoint loc;
|
||||
NSPoint rawPos;
|
||||
|
||||
// If no event given then get current mouse pos.
|
||||
if (eventOrNil == nil)
|
||||
{
|
||||
NSPoint rawPos = [[self window] mouseLocationOutsideOfEventStream];
|
||||
loc = [self convertPoint:rawPos fromView:nil];
|
||||
}
|
||||
rawPos = [[self window] mouseLocationOutsideOfEventStream];
|
||||
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.
|
||||
float h = [self frame].size.height;
|
||||
loc.y = h - loc.y;
|
||||
|
@ -66,6 +66,37 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
-(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
|
||||
///
|
||||
|
@ -52,6 +52,19 @@ namespace sf {
|
||||
/// implementation. However, all attributes are defined in the main
|
||||
/// 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
|
||||
{
|
||||
@ -61,6 +74,9 @@ namespace sf {
|
||||
NSTrackingArea* m_trackingArea; ///< Mouse tracking area
|
||||
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)
|
||||
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.
|
||||
// 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
|
||||
///
|
||||
/// \return the mouse position in SFML coord system
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
-(NSPoint)cursorPositionFromEvent:(NSEvent*)eventOrNil;
|
||||
|
||||
@ -154,4 +172,22 @@ namespace sf {
|
||||
////////////////////////////////////////////////////////////
|
||||
-(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
|
||||
|
@ -108,6 +108,9 @@
|
||||
|
||||
m_fullscreen = isFullscreen;
|
||||
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
|
||||
m_silentResponder = [[SFSilentResponder alloc] init];
|
||||
@ -163,8 +166,9 @@
|
||||
name:NSWindowDidChangeScreenProfileNotification
|
||||
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 updateCursorGrabbed]; // update for fullscreen
|
||||
}
|
||||
|
||||
|
||||
@ -245,6 +249,7 @@
|
||||
|
||||
// Update mouse internal state.
|
||||
[self updateMouseState];
|
||||
[self updateCursorGrabbed];
|
||||
|
||||
// Update the OGL view to fit the new size.
|
||||
[self update];
|
||||
@ -263,6 +268,8 @@
|
||||
{
|
||||
(void)notification;
|
||||
|
||||
[self updateCursorGrabbed];
|
||||
|
||||
if (m_requester)
|
||||
m_requester->windowGainedFocus();
|
||||
|
||||
@ -276,6 +283,8 @@
|
||||
{
|
||||
(void)notification;
|
||||
|
||||
[self updateCursorGrabbed];
|
||||
|
||||
if (m_requester)
|
||||
m_requester->windowLostFocus();
|
||||
|
||||
|
@ -121,6 +121,13 @@
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
-(void)setCursorGrabbed:(BOOL)grabbed
|
||||
{
|
||||
[m_oglView setCursorGrabbed:grabbed];
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
-(NSPoint)position
|
||||
{
|
||||
|
@ -49,6 +49,11 @@ namespace sf {
|
||||
/// Used when SFML handle everything and when a NSWindow* is given
|
||||
/// 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>
|
||||
{
|
||||
@ -56,6 +61,7 @@ namespace sf {
|
||||
SFOpenGLView* m_oglView; ///< OpenGL view for rendering
|
||||
sf::priv::WindowImplCocoa* m_requester; ///< Requester
|
||||
BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not
|
||||
BOOL m_restoreResize; ///< See note above
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
@ -97,6 +97,8 @@
|
||||
m_window = nil;
|
||||
m_oglView = nil;
|
||||
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.
|
||||
m_window = [window retain];
|
||||
@ -148,6 +150,7 @@
|
||||
m_oglView = nil;
|
||||
m_requester = 0;
|
||||
m_fullscreen = (style & sf::Style::Fullscreen);
|
||||
m_restoreResize = NO;
|
||||
|
||||
if (m_fullscreen)
|
||||
[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
|
||||
{
|
||||
@ -379,6 +405,9 @@
|
||||
|
||||
// Place the window.
|
||||
[m_window setFrameTopLeftPoint:point];
|
||||
|
||||
// In case the cursor was grabbed we need to update its position
|
||||
[m_oglView updateCursorGrabbed];
|
||||
}
|
||||
|
||||
|
||||
|
@ -316,7 +316,7 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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);
|
||||
|
@ -489,7 +489,7 @@ void WindowImplCocoa::setMouseCursorVisible(bool visible)
|
||||
////////////////////////////////////////////////////////////
|
||||
void WindowImplCocoa::setMouseCursorGrabbed(bool grabbed)
|
||||
{
|
||||
|
||||
[m_delegate setCursorGrabbed:grabbed];
|
||||
}
|
||||
|
||||
|
||||
|
@ -99,8 +99,10 @@ namespace sf {
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \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
|
||||
|
Loading…
Reference in New Issue
Block a user