且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

NSButton-自定义形状按钮的点击测试翻转/悬停

更新时间:2023-11-10 10:37:28

使用 NSTrackingArea 是必经之路,对于非矩形形状,您将必须添加自定义代码以对自定义几何进行命中测试。



不清楚当鼠标悬停在形状上方时为什么要连续设置相同的图像吗?



无论如何,有 mouseMoved :在鼠标在您的跟踪区域内移动时得到通知。
不过,请检查文档-您可能需要在视图或窗口上设置一些额外的标志,因为默认行为可能不会发送移动的消息,以防万一没人收听的情况下大量消息阻塞了框架。 p>

(Targeting OS X 10.10, latest version of Xcode)

I have an NSButton subclass.

The button needs to respond to mouse rollovers/hovers, ala a web button.

The button requires an appearance in the shape of a circle. Accordingly, -setImage: and -setAlternateImage: are called with circular-shaped graphics.

Problem 1: the default behaviour of NSButton results in a positive hit-test when the mouse is clicked anywhere within the rect of the NSButton. This includes mouse clicks that are outside the circular region of the button.

Proposed solution: override -hitTest: to calculate distance of click from centre of the button. If distance > button's radius, we don't have a valid click.

OK, fine. That provides us with accurate collision detection on mouse clicks on circular buttons.

Problem 2: how to handle mouse hover/rollover, and changing the appearance of the button accordingly.

Proposed solution: add an NSTrackingArea to the button, and then override -mouseEntered: and -mouseExited: so we can change the button image and alternateImage as the mouse moves over the button.

OK, fine. That works for rectangles.

But... NSTrackingArea works on rectangles, not arbitrary regions such a circle. Worse, it does NOT honour -hitTest:

Proposed solution: add the circular collision detection code in to -mouseEntered: and -mouseExited:

But...

-mouseEntered: and -mouseExited: are only called ONCE per button entry/exit, not continuously. This means that if the mouse is moved just in to the button's rectangular region, but not far enough that it enters the button's circular region, -mouseEntered: will be called. But it will not be called again, until after -mouseExited: is called. Further movement of the mouse in to the circular region will have no effect. (No rollover will occur.)

i.e. it is not possible to continuously update the button's state with accurate mouse position information using these two methods.

QUESTION: does anyone know how to implement hover and pressing on a non-rectangular button?

[edit - SOLVED thanks to cacau.]

Solution steps:

  1. Add an NSTrackingArea to the button, with (at a minimum) the NSTrackingMouseEnteredAndExited and NSTrackingMouseMoved options.

  2. Implement -hitTest: in order to do accurate (e.g. circular) hit testing for button presses. We do not change the appearance of the button here.

  3. Implement -mouseEntered: and -mouseExited:

  4. Do (circular) collision detection within -mouseEntered, changing the appearance of the button accordingly via -setImage: (or otherwise flagging changes to your drawing strategy.)

  5. On -mouseExited: set the button's appearance back to its default.

  6. But, -mouseEntered: and -mouseExited: are not called continuously. Therefore we also need to implement -mouseMoved:

  7. But, we must call -setAcceptsMouseMovedEvents:YES on the window, so that the -mouseMoved: method is called on the button.

  8. Add the (circular) collision detection and appearance changing to -mouseMoved as well.

Using NSTrackingArea is the way to go, for non-rectangular shapes you'll have to add custom code to perform hit testing for your custom geometries.

Not clear why you'd want to continuously set the same image when the mouse is hovering above your shape?

Anyway, there's mouseMoved: to get notified when the mouse moves within you tracking area. Check the docs, though - you might need to set some extra flags on the view or window as the default behaviour might not send moved messages to not clog the framework with huge amount of messages in case nobody listens..