21

I have an NSView that is catching a mouseDown event.

I'm getting the coordination of the mouse click using:

- (void)mouseDown:(NSEvent *)theEvent {
    NSPoint touchPoint = [NSEvent mouseLocation];
    //Rest of code
}

However, this sets clickPoint as the location of the mouse in the global screen coordination.

I want to get the position of the click in the relative view coordination - If the user clicks in the bottom left corner of the window, I want the point to be (0, 0) no matter where the containing window is on the screen.

Niv
  • 2,294
  • 5
  • 29
  • 41

3 Answers3

35

You should be using locationInWindow, not mouseLocation. The docs on locationInWindow show how to go from there:

NSPoint event_location = [theEvent locationInWindow];
NSPoint local_point = [self convertPoint:event_location fromView:nil];
JWWalker
  • 22,385
  • 6
  • 55
  • 76
  • Is the resulting point relative to the view's `frame` or `bounds`? – Ky - Sep 13 '19 at 15:47
  • 1
    @BenLeggiero, a view only has one coordinate system, and the point is in that coordinate system. `bounds` is in that coordinate system, while `frame` is in the superview's coordinate system. – JWWalker Sep 13 '19 at 22:38
  • Does not work for me, self is the view i'm receiving the mouse events in. with the window coordinates, but after conversion i get the same coordinates as the window. Am i missing something? – Cristi Băluță Aug 28 '20 at 08:02
  • @CristiBăluță, if the view is the content view of the window, or some other view that fills the window, then I think you should expect to get the same coordinates. – JWWalker Aug 28 '20 at 17:45
1

Swift

override func mouseMoved(with event: NSEvent) {
    let mouseLocation = event.locationInWindow
    print(mouseLocation)
}
ixany
  • 5,433
  • 9
  • 41
  • 65
1

Simply use [self convertPoint:event_location fromView:nil]; can be inaccurate, this can happen when event's window and view's window are not the same. Following is a more robust way to get the event location even in different window/screens:

public extension NSEvent {

  /// Get the event mouse location in `view`.
  func location(in view: NSView) -> CGPoint {
    if let eventWindow = window, let viewWindow = view.window {
      if eventWindow.windowNumber == viewWindow.windowNumber {
        // same window, just convert
        return view.convert(locationInWindow, from: nil)
      } else {
        // window not equal, check screen
        if let eventScreen = eventWindow.screen, let viewScreen = viewWindow.screen {
          if eventScreen.isEqual(to: viewScreen) {
            // same screen, try to convert between windows
            // screen coordinate zero point is at bottom left corner
            let eventLocationInScreen = locationInWindow.translate(dx: eventWindow.frame.origin.x, dy: eventWindow.frame.origin.y)
            let viewFrameInScreen = view.frameInWindow.translate(dx: viewWindow.frame.origin.x, dy: viewWindow.frame.origin.y)
            return eventLocationInScreen.translate(dx: -viewFrameInScreen.origin.x, dy: -viewFrameInScreen.origin.y)
          } else {
            // different screen, try to convert to unified coordinate
            let eventLocationInScreen = locationInWindow.translate(dx: eventWindow.frame.origin.x, dy: eventWindow.frame.origin.y)
            let eventLocationInBase = eventLocationInScreen.translate(dx: eventScreen.frame.origin.x, dy: eventScreen.frame.origin.y)

            let viewFrameInScreen = view.frameInWindow.translate(dx: viewWindow.frame.origin.x, dy: viewWindow.frame.origin.y)
            let viewFrameInBase = viewFrameInScreen.translate(dx: viewScreen.frame.origin.x, dy: viewScreen.frame.origin.y)
            return eventLocationInBase.translate(dx: -viewFrameInBase.origin.x, dy: -viewFrameInBase.origin.y)
          }
        }
      }
    }

    // other unexpected cases, fall back to use `convert(_:from:)`
    return view.convert(locationInWindow, from: nil)
  }
}

public extension NSView {

  /// The view's frame in its window.
  var frameInWindow: CGRect {
    convert(bounds, to: nil)
  }
}

public extension CGRect {

  /// Move/translate a `CGRect` by its origin..
  /// - Parameters:
  ///   - dx: The delta x.
  ///   - dy: The delta y.
  /// - Returns: A new `CGRect` with moved origin.
  func translate(dx: CGFloat = 0, dy: CGFloat = 0) -> CGRect {
    CGRect(origin: origin.translate(dx: dx, dy: dy), size: size)
  }
}

public extension CGPoint {

  /// Translate the point.
  /// - Parameters:
  ///   - dx: The delta x.
  ///   - dy: The delta y.
  /// - Returns: The translated point.
  @inlinable
  @inline(__always)
  func translate(dx: CGFloat = 0, dy: CGFloat = 0) -> CGPoint {
    CGPoint(x: x + dx, y: y + dy)
  }
}

As you can see, it checks if both event and view's window is the same one, if not, the logic tries to compare the screen and try to convert manually.

Honghao Z
  • 1,419
  • 22
  • 29