Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highlighting a NSMenuItem with a custom view?

I have created a simple NSStatusBar with a NSMenu set as the menu. I have also added a few NSMenuItems to this menu, which work fine (including selectors and highlighting) but as soon as I add a custom view (setView:) no highlighting occurs.

CustomMenuItem *menuItem = [[CustomMenuItem alloc] initWithTitle:@"" action:@selector(openPreferences:) keyEquivalent:@""];
[menuItem foo];
[menuItem setTarget:self];
[statusMenu insertItem:menuItem atIndex:0];
[menuItem release];

And my foo method is:

- (void)foo {
  NSView *view = [[NSView alloc] initWithFrame:CGRectMake(5, 10, 100, 20)];
  [self setView:view];
}

If I remove the setView method, it will highlight.

I have searched and searched and cannot find a way of implementing/enabling this.

Edit

I implemented highlight by following the code in this question in my NSView SubClass:

An NSMenuItem's view (instance of an NSView subclass) isn't highlighting on hover

#define menuItem ([self enclosingMenuItem])

- (void) drawRect: (NSRect) rect {
    BOOL isHighlighted = [menuItem isHighlighted];
    if (isHighlighted) {
        [[NSColor selectedMenuItemColor] set];
        [NSBezierPath fillRect:rect];
    } else {
        [super drawRect: rect];
    }
}
like image 243
rdougan Avatar asked May 19 '11 05:05

rdougan


4 Answers

Here's a rather less long-winded version of the above. It's worked well for me. (backgroundColour is an ivar.)

- (void)drawRect:(NSRect)rect
{
    if ([[self enclosingMenuItem] isHighlighted]) {
        [[NSColor selectedMenuItemColor] set];
    } else if (backgroundColour) {
        [backgroundColour set];
    }
    NSRectFill(rect);
}
like image 197
Lester Corncrake Avatar answered Sep 25 '22 04:09

Lester Corncrake


Update for 2019:

class CustomMenuItemView: NSView {
    private var effectView: NSVisualEffectView

    override init(frame: NSRect) {
        effectView = NSVisualEffectView()
        effectView.state = .active
        effectView.material = .selection
        effectView.isEmphasized = true
        effectView.blendingMode = .behindWindow

        super.init(frame: frame)
        addSubview(effectView)
        effectView.frame = bounds
    }

    required init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ dirtyRect: NSRect) {
        effectView.isHidden = !(enclosingMenuItem?.isHighlighted ?? false)
    }
}

Set one of those to your menuItem.view.

(Credit belongs to Sam Soffes who helped me figure this out and sent me almost that code verbatim.)

like image 37
Andrew Avatar answered Sep 23 '22 04:09

Andrew


If you're adding a view to a menu item, that view has to draw the highlight itself. You don't get that for free, I'm afraid. From the Menu Programming Topics:

A menu item with a view does not draw its title, state, font, or other standard drawing attributes, and assigns drawing responsibility entirely to the view.

like image 23
jscs Avatar answered Sep 25 '22 04:09

jscs


Yes, as mentioned earlier you must draw it yourself. I use AppKit's NSDrawThreePartImage(…) to draw, and also include checks to use the user's control appearance (blue or graphite.) To get the images, I just took them from a screenshot (if anyone knows a better way, please add a comment.) Here's a piece of my MenuItemView's drawRect:

    // draw the highlight gradient
if ([[self menuItem] isHighlighted]) {

    NSInteger tint = [[NSUserDefaults standardUserDefaults] integerForKey:@"AppleAquaColorVariant"];
    NSImage *image = (AppleAquaColorGraphite == tint) ? menuItemFillGray : menuItemFillBlue;

    NSDrawThreePartImage(dirtyRect, nil, image, nil, NO,
        NSCompositeSourceOver, 1.0, [self isFlipped]);
}
else if ([self backgroundColor]) {

    [[self backgroundColor] set];
    NSRectFill(dirtyRect);
}

EDIT

Should have defined these:

enum AppleAquaColorVariant {
    AppleAquaColorBlue = 1,
    AppleAquaColorGraphite = 6,
};

These correspond to the two appearance options in System Preferences. Also, menuItemFillGray & menuItemFillBlue are just NSImages of the standard menu item fill gradients.

like image 23
Francis McGrew Avatar answered Sep 25 '22 04:09

Francis McGrew