Как сделать tracking area для NSBezierPath c изменением курсора мыши ?


#1

Есть костюмный NSView. В нем прописываем код для детекции события когда курсор мыши, при ее перемещении, попадая на отрезок (его NSBezierPath) изменяется и когда вне NSBezierPath этого отрезка восстанавливает прежний, дефолтный вид.

override func viewDidMoveToWindow()
{
    let trackingArea = NSTrackingArea(rect: CGRect(x: 0.0, y: 0.0, width: self.bounds.width, height: self.bounds.height), options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved], owner: self, userInfo: nil)
    self.addTrackingArea(trackingArea)

}

override func mouseEntered(with event: NSEvent)
{
    self.window?.acceptsMouseMovedEvents = true
    self.window?.makeFirstResponder(self)
    
}

override func mouseMoved(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) == true
            {
                NSCursor.pointingHand().set()
            }
            else if each.1.contains(mouseLocationNSPoint) != true
            {
                NSCursor.arrow().set()
            }
        }
    }
}

С кодом выше есть две проблемы.

Во-первых, изменение курсора мыши происходит нормально только для одного отрезка - подводим мышь - курсор меняется, убираем - возвращается к дефолтному виду. И этот отрезок самый нижний. Для других отрезков, курсор подрагивает - то нормальный, то измененный, убираем получаем нормальный (дефолтный).

Во-вторых, размер NSView больше чем видимая часть, соотвественно есть скроллинг по вертикали и горизонтали. Если мышь не в видимой части, то видим что есть детекция отрезков вне видимой части NSView - подрагивание курсора мыши. Я так понимаю это связано с self.window?.makeFirstResponder(self). Если убираем self.window?.makeFirstResponder(self), то детектируется только начальная видимая часть и если скролить то отрезки исходно скрытые не детектируются.

Вообщем, запутался я с этим tracking area для NSBezierPath. Нужна помощь !!!


#2

Модифицировал код

override func viewDidMoveToWindow()
{
    let trackingArea = NSTrackingArea(rect: CGRect(x: 0.0, y: 0.0, width: self.bounds.width, height: self.bounds.height), options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways, .inVisibleRect, .cursorUpdate], owner: self, userInfo: nil)
    self.addTrackingArea(trackingArea)
}

override func mouseEntered(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) == true
            {
                NSCursor.pointingHand().set()
            }
        }
    }
}

override func mouseExited(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) != true
            {
                NSCursor.arrow().set()
            }
        }
    }
}


override func mouseMoved(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) == true
            {
                NSCursor.pointingHand().set()
            }
            else if each.1.contains(mouseLocationNSPoint) == false
            {
                NSCursor.arrow().set()
            }
        }
    }
}

override func cursorUpdate(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) == true
            {
                NSCursor.pointingHand().set()
            }
        }
    }
}

Вторая проблема решена за счет добавления .inVisibleRect. Кроме того необходимо прописать

override func mouseExited(with event: NSEvent)
{
    let mouseLocationNSPoint = self.convert(event.locationInWindow, from: nil)
    
    if arrayOfNames_NSBezierPaths.isEmpty != true
    {
        for each in arrayOfNames_NSBezierPaths
        {
            if each.1.contains(mouseLocationNSPoint) != true
            {
                NSCursor.arrow().set()
            }
        }
    }
}

Можно убрать две строчки ниже. Насколько я понял раньше была привязка к window, теперь все привязано к view независимо от того в каком window находится view.

self.window?.acceptsMouseMovedEvents = true
self.window?.makeFirstResponder(self)

Добавление .cursorUpdate приводит к тому что при нажатии pointingHand остается, если не добавлять то при нажатии меняется на дефолтный вид.

Но первая проблема так и остается - изменение курсора мыши происходит нормально только для одного отрезка - подводим мышь - курсор меняется, убираем - возвращается к дефолтному виду. И этот отрезок самый нижний. Для других отрезков, курсор подрагивает - то нормальный, то измененный, убираем получаем нормальный (дефолтный). С чем это может быть связано никаких идей нет.

В который раз убеждаюсь, что Apple никак не хочет делать жизнь разработчиков программ легче, скорее наоборот. Может я конечно плохо читаю их устаревшие инструкции, но все же мне так не кажется.

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/EventOverview/TrackingAreaObjects/TrackingAreaObjects.html


#3

Ха ! А ларчик просто открывался :). Первая проблема решается просто - добавляем break !

   if each.1.contains(mouseLocationNSPoint) == true
  {
      NSCursor.pointingHand().set()
      break
  }

Самый нижний отрезок - это последний элемент в массиве. Поэтому, как только выполняется условие выше нужен break и тогда pointingHand() появляется для каждого отрезка.