How to move the cursor to the center of an NSView? - swift

On macOS, I have an application that should move the cursor to the center of the NSView when the window is loaded.
class ViewController: NSViewController {
override func viewDidLayout() {
super.viewDidLayout()
let view = super.view
let point = NSPoint(x: view.frame.midX, y:view.frame.midY)
let pointInWindow = view.convert(point, to: nil)
let pointOnScreen = view.window?.convertToScreen(NSRect(origin: pointInWindow, size: .zero)).origin ?? .zero
CGWarpMouseCursorPosition(NSPoint(x:pointOnScreen.x, y:pointOnScreen.y))
}
}
(The code was taken from: Mac OS X: Convert between NSView coordinates and global screen coordinates)
But it seems to be placing the cursor above the window instead of inside it— x-position looks correct but the y-position is off. Thank you for any help.

I found a usage example on GitHub by searching for CGWarpMousePosition:
https://github.com/chockenberry/Notchmeister/blob/9e9308f0803a4e0faf27790c02081689545a989d/Notchmeister/Notchmeister/PortalEffect.swift#L162-L171
This ended up working. The y-position needed to be subtracted from the bottom screen coordinate:
class ViewController: NSViewController {
override func viewDidLayout() {
super.viewDidLayout()
let view = super.view
let window = view.window
let screen = window!.screen
let viewPoint = NSPoint(x: view.frame.midX, y:view.frame.midY)
let windowPoint = view.convert(viewPoint, to: nil)
let screenPoint = window!.convertPoint(toScreen: windowPoint)
let globalPoint = CGPoint(
x: screen!.frame.origin.x + screenPoint.x,
y: screen!.frame.origin.y + screen!.frame.height - screenPoint.y
)
CGWarpMouseCursorPosition(globalPoint)
}
}

Related

ARKit – How to know if 3d object is in the center of a screen?

I place 3d object in the world space. After that I try to move camera randomly. Then right now I need to know after I knew object has became inside frustum by isNode method, if the object is in center, top or bottom of camera view.
For a solution that's not a hack you can use the projectPoint: API.
It's probably better to work with pixel coordinates because this method uses the actual camera's settings to determine where the object appears on screen.
let projectedPoint = sceneView.projectPoint(self.sphereNode.worldPosition)
let xOffset = projectedPoint.x - screenCenter.x;
let yOffset = projectedPoint.y - screenCenter.y;
if xOffset * xOffset + yOffset * yOffset < R_squared {
// inside a disc of radius 'R' at the center of the screen
}
Solution
To achieve this you need to use a trick. Create new SCNCamera, make it a child of pointOfView default camera and set its FoV to approximately 10 degrees.
Then inside renderer(_:updateAtTime:) instance method use isNode(:insideFrustumOf:) method.
Here's working code:
import ARKit
class ViewController: UIViewController,
ARSCNViewDelegate,
SCNSceneRendererDelegate {
#IBOutlet var sceneView: ARSCNView!
#IBOutlet var label: UILabel!
let cameraNode = SCNNode()
let sphereNode = SCNNode()
let config = ARWorldTrackingConfiguration()
public func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
DispatchQueue.main.async {
if self.sceneView.isNode(self.sphereNode,
insideFrustumOf: self.cameraNode) {
self.label.text = "In the center..."
} else {
self.label.text = "Out OF CENTER"
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.allowsCameraControl = true
let scene = SCNScene()
sceneView.scene = scene
cameraNode.camera = SCNCamera()
cameraNode.camera?.fieldOfView = 10
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.sceneView.pointOfView!.addChildNode(self.cameraNode)
}
sphereNode.geometry = SCNSphere(radius: 0.05)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
sphereNode.position.z = -1.0
sceneView.scene.rootNode.addChildNode(sphereNode)
sceneView.session.run(config)
}
}
Also, in this solution you may turn on an orthographic projection for child camera, instead of perspective one. It helps when a model is far from the camera.
cameraNode.camera?.usesOrthographicProjection = true
Here's how your screen might look like:
Next steps
The same way you can append two additional SCNCameras, place them above and below central SCNCamera, and test your object with two extra isNode(:insideFrustumOf:) instance methods.
I solved problem with another way:
let results = self.sceneView.hitTest(screenCenter!, options: [SCNHitTestOption.rootNode: parentnode])
where parentnode is the parent of target node, because I have multiple nodes.
func nodeInCenter() -> SCNNode? {
let x = (Int(sceneView.projectPoint(sceneView.pointOfView!.worldPosition).x - sceneView.projectPoint(sphereNode.worldPosition).x) ^^ 2) < 9
let y = (Int(sceneView.projectPoint(sceneView.pointOfView!.worldPosition).y - sceneView.projectPoint(sphereNode.worldPosition).y) ^^ 2) < 9
if x && y {
return node
}
return nil
}

Detect SCNNode with camera only

Let's say I draw a circle in the middle of my screen, to use it as a target. If I point this circle to a node, how is it possible for ARKit to detect it?
For now I'm using the tap method
#IBAction func tapHandler(_ sender: UITapGestureRecognizer) {
let viewTouchLocation:CGPoint = sender.location(in: sceneView)
guard let result = sceneView.hitTest(viewTouchLocation, options: nil).first else {
return
}
// ...etc
}
which works really well, but it would be so much better to detect a node just by pointing the camera at it.
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width
let screenHeight = screenRect.size.height
let location = CGPoint(x:screenWidth/2,y:screenHeight/2)
use location in hittest

SystemStatusBar statusItem title being cut short on OS X

I am trying to display an OS X application statusItem in the System Status Bar and am having success with everything except the fact that the title is being cut off. I am initializing everything like so:
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
func applicationDidFinishLaunching(aNotification: NSNotification) {
let icon = NSImage(named: "statusIcon")
icon?.template = true
statusItem.image = icon
statusItem.menu = statusMenu
statusItem.title = "This is a test title"
}
The problem is the statusItem.title is appearing like so:
As you can see the application next to mine (iStatMenuBar) is cutting off the title to my application (or something similar is happening)
If I comment out the icon for the statusItem, it works and shows the entire title but when I re-add the icon it cuts off again. Is there a way for the two (icon and title) to co exist? I have reviewed some Apple docs and may have missed a critical piece which explains this.
Thanks guys.
One option would be to assign a custom view to your statusBarItem and within that view's class override drawRect(dirtyRect: NSRect) e.g.
private var icon:StatusMenuView?
let bar = NSStatusBar.systemStatusBar()
item = bar.statusItemWithLength(-1)
self.icon = StatusMenuView()
item!.view = icon
and StatusMenuView might look like:
// This is an edited copy & paste from one of my personal projects so it might be missing some code
class StatusMenuView:NSView {
private(set) var image: NSImage
private let titleString:NSString = "really long title..."
init() {
icon = NSImage(named: "someImage")!
let myWideStatusBarItemFrame = CGRectMake(0, 0, 180.0, NSStatusBar.systemStatusBar().thickness)
super.init(frame.rect)
}
override func drawRect(dirtyRect: NSRect)
{
self.item.drawStatusBarBackgroundInRect(dirtyRect, withHighlight: self.isSelected)
let size = self.image.size
let rect = CGRectMake(2, 2, size.width, size.height)
self.image.drawInRect(rect)
let titleRect = CGRectMake( 2 + size.width, dirtyRect.origin.y, 180.0 - size.width, size.height)
self.titleString.drawInRect(titleRect, withAttributes: nil)
}
}
Now, the above might change your event handling, you'll need to handle mouseDown in the StatusMenuView class.

Detect if user is moving finger left or right (Swift)

This is not Sprite Kit.
If I have a variable like the one below
var value = 0
How am I able to increase the value if the user drags right and decrease if they drag left?
Thanks!
Like Caleb commented, Ray's tutorial is great, but if you want the actual swift example, please check the next example:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
private var value: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
let recognizer = UIPanGestureRecognizer(target: self, action: Selector("handleDragging:"))
let inputView = UIView(frame: CGRectMake(0, 0, 100, 100))
inputView.backgroundColor = UIColor.whiteColor()
inputView.userInteractionEnabled = true
inputView.addGestureRecognizer(recognizer)
self.view.addSubview(inputView)
}
func handleDragging(recognizer: UIPanGestureRecognizer) {
if (recognizer.state == .Changed) {
let point = recognizer.velocityInView(recognizer.view?.superview)
if (point.x > 0) {
self.value++;
} else {
self.value--;
}
println(self.value)
}
}
}
You can use the velocityInView method of UIPanGestureRecognizer to determine which direction you're going. It returns a CGPoint, so you can pull out the x and y values as you wish. Positive is right/down, negative is left/up.

Converting UIKit to SceneKit

Trouble converting program from UIKit to SceneKit. Biggest difficulty for me is understanding how delegate file, Tile, synched with array, Board, is set up with SceneKit. It is a simple project. A screenshot: http://imgur.com/9hsv7X5. It displays a 3 x 5 array. User taps an item and it becomes highlighted. Then tap another item, it becomes highlighted, previous item, unhighlighted.
Here is the UIKit project composed of 3 files:
VIEWCONTROLLER
import UIKit
struct BoardLoc {
var x: Int
var y: Int
}
class ViewController: UIViewController, TileDelegate {
var tile: Tile!
override func viewDidLoad() {
super.viewDidLoad()
let scene = Board()
tile.tileDelegate = self
tile.board = scene
}
func getTileAtLoc(tile: Tile, _ boardLoc: BoardLoc) {
tile.boardLoc = boardLoc
}
}
BOARD
import Foundation
class Board {
var board: Array<Array<String>> = Array(count:3, repeatedValue:Array(count:5, repeatedValue:"foo"))
func putTileAt(boardLoc: BoardLoc) -> String {
return board[boardLoc.x][boardLoc.y]
}
}
TILE
import UIKit
protocol TileDelegate {
func getTileAtLoc(tile: Tile, _ boardLoc: BoardLoc)
}
class Tile: UIView {
var boardLoc: BoardLoc?
var board: Board?
var tileDelegate: TileDelegate?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addGestureRecognizer(UITapGestureRecognizer(target: self, action:"handleTap:"))
}
override func drawRect(rect: CGRect) {
for x in 0...2 {
for y in 0...4 {
let context = UIGraphicsGetCurrentContext()
let red = UIColor.redColor().CGColor
let orange = UIColor.orangeColor().CGColor
let bigCircle = CGRectMake(CGFloat(106 * x),CGFloat(106 * y), 106, 106)
let smallCircle = CGRectMake(CGFloat(106 * x) + 3, CGFloat(106 * y) + 3, 100, 100)
if (boardLoc != nil && boardLoc!.x == x && boardLoc!.y == y) {
CGContextSetFillColorWithColor(context, red)
CGContextFillEllipseInRect(context, bigCircle)
}
if board!.putTileAt(BoardLoc(x: x, y: y)) == "foo" {
CGContextSetFillColorWithColor(context, orange)
CGContextFillEllipseInRect(context, smallCircle)
}
}
}
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let point = gestureRecognizer.locationInView(self)
let boardLoc = BoardLoc(x: Int(point.x) / 106, y: Int(point.y) / 106)
tileDelegate!.getTileAtLoc(self, boardLoc)
setNeedsDisplay()
}
}
First of all, I recommend you to read Apple SceneKit document and some tutorials.
Scene Kit is a 3D-rendering Objective-C framework that combines a high-performance rendering engine with a high-level, descriptive API. Scene Kit supports the import, manipulation, and rendering of 3D assets without requiring the exact steps to render a scene the way OpenGL does.
http://www.objc.io/issue-18/scenekit.html
https://www.weheartswift.com/introduction-scenekit-part-1/
http://www.raywenderlich.com/83748/beginning-scene-kit-tutorial
http://tacow.org/assets/attachments/SceneKit.pdf
Scene Kit allows you to render 3D scene easily, without OpenGL ES APIs. However you should understand how Scene Kit works.
Basically, Scene Kit provides a view controller that maintains an animation loop. This loop follows a design pattern common in games and simulations, with two phases: update and render. In the implementation, Scene Kit has more phases like the following figure (from http://www.objc.io/issue-18/scenekit.html), but basically, two phases, update and render.
So how to create Scene Kit project, the basics is
Prepare SCNView
Initialize 3D scene
Create touch event handler
Implement Update phase: Update game board using the touched object or the touched position, Update the animation of the objects, or some sort of stuff.
Implement Render phase: Basically, Scene Kit automatically renders registered 3D objects and models.
Thus, you should implement as the following.
Use SCNView instead of ViewController
Create a scene
Place Board and Tiles as Scene Kit 3D objects
Use hitTest for touching Tile and update Tiles in Update phase
import SceneKit
class GameViewController: UIViewController {
struct BoardLoc {
var x: Int
var y: Int
}
enum Type {
case Yellow
case Orange
}
var boardArray: Array<Array<Type>> = []
override func viewDidLoad() {
super.viewDidLoad()
for x in 0...2 {
boardArray.append(Array(count:5, repeatedValue:Type.Orange))
for y in 0...4 {
boardArray[x][y] = Type.Orange
}
}
let scene = SCNScene(named: "art.scnassets/balls8.dae")
let scnView = self.view as SCNView
scnView.scene = scene
scnView.autoenablesDefaultLighting = true
let taps = NSMutableArray()
let tap = UITapGestureRecognizer(target: self, action: "handleTap:")
taps.addObject(tap)
scnView.gestureRecognizers = taps
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let scnView = view as SCNView
let point = gestureRecognizer.locationInView(scnView)
if let hitResults = scnView.hitTest(point, options: nil) {
if hitResults.count > 0 {
let result: AnyObject! = hitResults[0]
if !result.node!.name!.hasPrefix("Orange") {
return
}
let tapLoc = BoardLoc(x: Int(point.x) / 106, y: Int(point.y) / 106)
boardArray[tapLoc.x][tapLoc.y] = Type.Yellow
for col in 0...2 {
for row in 0...4 {
var yellowBall = scnView.scene!.rootNode.childNodeWithName("Yellow", recursively: true)
var secxtorX = Float(col) * 16.5 - 16
var sectorY = 34 - (Float(row) * 16.5)
if boardArray[col][row] == Type.Yellow {
yellowBall!.runAction(SCNAction.moveTo(SCNVector3(x: secxtorX, y: sectorY, z: 25), duration: 0.01))
boardArray[tapLoc.x][tapLoc.y] = Type.Orange
}
}
}
}
}
}
}