How to detect active appearance for NSView or for its parent NSWindow?
Asked Answered
D

2

7

All native controls have different appearance when their parent window is active or inactive. How should we check this state in custom components e.g. while rendering a button cell?

We could inspect controlView.window’s properties like isMainWindow and isKeyWindow, but they don’t cover all cases. For instance, if you open one window of the app on the Desktop and another in a full-screen Space, only one of them can be key or main according to public APIs. However, standard controls seem to render them as active in both Spaces:

Please note how toolbar buttons in both Safari windows are rendered as active. How do we achieve the same behavior?

Dyslogia answered 3/10, 2019 at 10:4 Comment(12)
Could you also inspect the windows full screen state?Mannerheim
@Mannerheim This is still not enough when you switch from the full screen Space to the Desktop 😒Dyslogia
I figured it was a dumb suggestion. How about developer.apple.com/documentation/appkit/nswindow/…Mannerheim
@Mannerheim I checked this one as well, but it does not work in a split full screen and, again, when you swipe from the full screen Space to the Desktop 🤷‍♂️Dyslogia
The question is not clear. Is the question about enabling/disabling buttons in toolbar? -> toolbarValidation. Is it about drawing? cell has enabled, highlighted property. It's set automatically and your cell drawing should not observe changes or even look or touch it's window (when drawing). PS: NSAppearance is more about dark, light themes. 3 different topics and even with picture it's not clear. Just my 2cents. PS: There is effectiveAppearance when drawing.Estrada
@MarekH The question is about rendering enabled toolbar buttons when the parent window is not active e.g. when your focus is in Preferences. It is not about toolbar validation nor about disabled or highlighted states, window appearance is also another topic.Dyslogia
@Vadim use Xcode to attach to Safari and use lldb [NSApp windows] ... to explore what they did. PS: filter windows with correct title. Check source code using hopper... (PrivateFrameworks/Safari.framework) -> it's full of nice objective c codeEstrada
@MarekH Not sure what you mean. Debugging active windows is close to impossible because you will want to switch between apps. I bet Safari uses a private API named NSWindow.hasActiveAppearance.Dyslogia
@Vadim you can set breakpoint 'br com add -m "-[Class method]" ' and the br com add 1, commands like "po $rdx", even one you use variables. Last command should be "continue" so Safari doesn't hang. Hit DONE and you have nice console output. lldb is really powerful. You can inspect XCode console output later.Estrada
@MarekH Disassembly works better, and this still doesn’t help with the original question. It looks like AppKit doesn’t provide any means to detect a visual state of the parent window when you render a control.Dyslogia
@Vadim -How do we achieve the same behavior? -> I gave you hints how to find out what they are doing. If I know the answer I would post it.Estrada
Seems like the NSWindow.hasActiveAppearance covers every cases. But the property is not KVC-compliant so we either need to find a private notification for it or use a timer to get updates.Jameejamel
D
2

Fortunately, SwiftUI allows to inherit a new magic property from the Environment:

/// Window state.
@Environment(\.controlActiveState)
var windowState: ControlActiveState

This is an official solution. Cheers!

Dyslogia answered 17/10, 2019 at 17:42 Comment(1)
This does not cover one case, which is 1. make your window active but not full screen; 2. switch to a full screen app; 3. Command-Tab to switch to another app on the same Space that have your active window on it -> the controlActiveState stays in key state while your window is no longer active.Jameejamel
U
0

I figure out this solution in Swift 5 (Use a NSButton instead of NSView)

class SomeView: NSButton {

    init() {
        super.init(frame: NSRect())
        self.isBordered = false

        // Setting `contentTintColor` can trigger `updateLayer()` when window's active state has changed
        self.contentTintColor = .labelColor
    }

    required init?(coder: NSCoder) {
        fatalError()
    }
    
    // Update view's appearance here
    override func updateLayer() {
        super.updateLayer()
        self.layer?.opacity = (self.window?.isMainWindow ?? false) ? 1 : 0.5
    }
}
Unstriped answered 24/5, 2023 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.