Getting [NSEvent mouseLocation] in view coordination system
Asked Answered
V

3

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.

Viguerie answered 21/5, 2014 at 15:37 Comment(0)
M
36

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];
Mam answered 21/5, 2014 at 17:50 Comment(4)
Is the resulting point relative to the view's frame or bounds?Damico
@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.Mam
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?Crosswind
@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.Mam
G
1

Swift

override func mouseMoved(with event: NSEvent) {
    let mouseLocation = event.locationInWindow
    print(mouseLocation)
}
Grantley answered 29/12, 2019 at 12:0 Comment(0)
L
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.

Lenora answered 31/10, 2021 at 6:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.