0

Is it possible for an NSMenu object to notify BEFORE it'll close, not after? Its delegate has method didClose(_:) but I want to update its items before it actually closes, since the disappearing animation is too long and the eye can see the change.

I've tried to monitor NSEvents, but it's useless because NSMenu hasn't public property containing its NSWindow object.

It's theoretically possible to achieve by creating a custom NSViews for each menu items. But I don't like this because then I'll have to draw all the drawing of the items, including selection and click animation.

UPDATE: I've tried to subclass the NSPopUpButton to track menu updates:

class CustomPopUpButton: NSPopUpButton {
    var isMenuShown: Bool = false
    var onClosingMenu: ((NSMenu)->())?
    
    override var needsDisplay: Bool {
        willSet {
            if let menu = self.menu, isMenuShown, newValue {
                onClosingMenu?(menu)
                isMenuShown = false
            }
        }
    }

    override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
        isMenuShown = true
        super.willOpenMenu(menu, with: event)
    }
}

I'm not proud of that piece of code but it works in general. Yet the 'onClosingMenu' method is being called just after the menu closing animation is finished. Not before.

Video of what I want to achieve: https://drive.google.com/file/d/1GAceKp-fTlurxSybdB3h0epVZrtQjthm/view?usp=sharing

Vitalii Vashchenko
  • 1,777
  • 1
  • 13
  • 23
  • I think you can try to do what you want in `NSMenu.willSendActionNotification` handler, because menu is closed once any item action sent. – Asperi Dec 08 '20 at 13:30
  • That works only if user clicks ON an item. But it's possible to close menu by clicking outside the menu, or by hitting the Esc key. And I'm stuck with that :( – Vitalii Vashchenko Dec 08 '20 at 13:33
  • 1
    It is not clear what do you want to achieve. NSMenu has own validation/udpate flow, so what's the problem? – Asperi Dec 08 '20 at 13:36
  • I want to display the picker of fonts available in the system. Let's say, like in Apple Pages. In that app NSPopupButton with NSMenu of available fonts is changing the display options of selected items depending on the menu showing state. If the menu is shown, selected item has an NSAttrbiutedString title. But before the menu closes it removes the attributes and when the menu is closed you see the normal button font, the default one. I was able to imitate that behaviour, except the title update when closing the menu. I do that in didClose method and the change of title's font is too visible – Vitalii Vashchenko Dec 08 '20 at 13:44
  • I still don't understand what you're trying to accomplish. – Willeke Dec 08 '20 at 14:11
  • If you look on the font picker button you'll see that by default it shows the font name of selected text with default system font. But as you know NSPopUpButton shows the selected NSMenuItem title, not just regular text. If you click on the button, you'll see the NSMenu with items representing available font names. But the title of that items has attributedTitle with corresponding font object for .font key. If you try to do that fonts popup button by yourself, you'll see that if you set attributedTitle for menu items the selected font name in the button is now has attributedTitle too. – Vitalii Vashchenko Dec 08 '20 at 14:15
  • But that's inappropriate. When the button is not presenting the menu, it's title shouldn't be attributed. To achieve that you have to set attributedTitle of the selected menu item before displaying the font menu (we have 'willOpen' method in NSMenuDelegate). and to remove attributedTitle BEFORE the menu closes. I'd suggest you click on the font picker button in Apple Pages to see that for yourself – Vitalii Vashchenko Dec 08 '20 at 14:17
  • I edited a video to show you the difference, so you could understand: https://drive.google.com/file/d/1GAceKp-fTlurxSybdB3h0epVZrtQjthm/view?usp=sharing – Vitalii Vashchenko Dec 08 '20 at 14:40

1 Answers1

0

Finally, I've found the solution. No need to fight the system and update something before the button menu closes. I've found the another way and it's pretty simple.

I've subclassed the NSPopUpButton and created another NSMenu in the subclass, called 'attributedMenu'. Overrided all properties of the NSPopUpButton that deal with menu items (insertions and removals) and redirected that actions to the 'attributedMenu' property.

The initial menu property of NSPopUpButton I'm using only for selected items, removing non-selected items right away.

I intercept the click on the button to show 'attributedMenu', not the default menu of the class.

That solution even made possible to display 'multiple values' title if I select more than one element. Like in Apple Pages' font picker when you select text written with multiple fonts. All it takes is to add an NSMenuItem with title 'Multiple Values' and call super to select it.

That's it, now it works as perfect as in Apple Pages font picker button. As long, as I'm not touching the original 'menu' property of NSPopUpButton class.

UPDATE

Uploaded the subclass to GitHub: https://github.com/CineDev/AttributedPopUpButton

Vitalii Vashchenko
  • 1,777
  • 1
  • 13
  • 23