Search code examples
swiftmacoscocoanswindow

How to trigger the hover state of a standardWindowButton?


The icon of the traffic light buttons aren't being shown when hovered on; you have to move your cursor the second time to show it.

What I am trying to do: I'm trying to change how the traffic lights in NSWindow behave. The color of the traffics lights will be clear when inactive, and shown when active.

  • By active I mean hovered upon.

Problem: The issue I'm having is that the icons of the buttons are not being shown, the colors on the other hand are shown. Look at the images below. You have to move your cursor the second time to have the icons displayed.

What I have tried: I have tried using .isHighlighted on the standardWindowButton, this does show the icons, however it changes the color to look like as if you clicked on that button; which I do not wish for. Look at the image below

Code: I am using a trackingRectTag on the closeButton that has a rect big enough to include the minimize and zoom buttons. By default, the buttons are disabled, and when hovered upon it will be enabled. The contentTintColor property does not work on these window buttons which is why I have to use isEnabled.

Source Code:

class AXWindow: NSWindow, NSWindowDelegate {
    var trackingTag: NSView.TrackingRectTag?
    
    init() {
        super.init(...)
        
        updateTrackingAreas(true)
        shouldEnableButtons(false)
    }
    
    override func mouseEntered(with theEvent: NSEvent) {
        if trackingTag == theEvent.trackingNumber {
            shouldEnableButtons(true)
        }
    }
    
    override func mouseExited(with theEvent: NSEvent) {
        if trackingTag == theEvent.trackingNumber {
            shouldEnableButtons(false)
        }
    }
    
    func updateTrackingAreas(_ establish : Bool) {
        if let tag = trackingTag {
            standardWindowButton(.closeButton)!.removeTrackingRect(tag)
        }
        
        if establish, let closeButton = standardWindowButton(.closeButton) {
            let newBounds = NSRect(x: 0, y: 0, width: 55, height: 14.5)
            trackingTag = closeButton.addTrackingRect(newBounds, owner: self, userData: nil, assumeInside: false)
        }
    }
    
    fileprivate func shouldEnableButtons(_ b: Bool) {
        standardWindowButton(.closeButton)!.isEnabled = b
        standardWindowButton(.miniaturizeButton)!.isEnabled = b
        standardWindowButton(.zoomButton)!.isEnabled = b
    }
}

Result Result

Expected Result Expected Result

Inactive State: Inactive State

Using .isHighlighted Using isHighlighted


Solution

  • I haven't found a way to change this through changing the NSWindowButton. However, I have found a workaround. The best way to achieve this is by having two NSWindows.

    We have a parent window and a child window. The application's contents will be located in the child window, and the parent window will be in the background.

    Make sure the child window's height is reduced to show the title bar.

    NSWindow with child window

    If you want to change the window's shape to show only the titleBar of the parent. You must look into different NSWindow shapes.