如何在选择后闪烁自定义 NSMenuItem 视图?

发布于 2024-10-15 07:18:27 字数 404 浏览 3 评论 0原文

我需要将视图分配给 NSMenuItem 并进行一些自定义绘图。基本上,我在当前选定的菜单项旁边添加了一个小删除按钮等。但我希望我的自定义菜单项在所有其他方面看起来和行为都像常规菜单项一样。根据文档:

带有视图的菜单项不会绘制 它的标题、状态、字体或其他 标准绘图属性,以及 分配绘图责任 完全符合视图。

好吧,所以我必须复制状态列和选择渐变的外观,这并不难。我遇到问题的部分是菜单项在被选择后“闪烁”或“闪烁”的方式。我正在使用 NSTimer 尝试模仿这个小动画,但感觉不太好。它眨眼多少次?我应该使用什么时间间隔?我已经尝试了很多,但感觉不正常。

有没有人以前做过此操作或对如何向菜单项添加按钮有其他建议?也许应该有一个专门用于定制可可绘图的堆栈交换站点......

I need to assign a view to an NSMenuItem and do some custom drawing. Basically, I'm adding a little delete button next to the currently selected menu item, among other things. But I want my custom menu item to look and behave like a regular menu item in all other ways. According to the doc:

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.

Ok, so I had to duplicate the look of the state column and the selection gradient, which wasn't that hard. The part I'm having trouble with is the way the menu item "flashes" or "blinks" after it is selected. I'm using an NSTimer to try to mimic this little animation, but it just feels off. How many times does it blink? What time interval should I use? I've experimented a lot and it just feels out of whack.

Has anyone done this before or have other suggestions on how to add a button to a menu item? Maybe there should be a stack exchange site just for custom cocoa drawing...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

盗心人 2024-10-22 07:18:27

我知道这已经有一年多了,但这是我的谷歌搜索的第一次点击并且没有得到答复,所以我发布我的答案是为了那些仍在寻找解决方案的人。

对于我的应用程序,我将 Core Animation 与 NSMenuItem 视图的自定义 NSView 结合使用。我创建了一个新的图层支持视图,设置背景颜色,并将其添加到我的自定义视图中。然后我对图层(闪烁部分)进行动画处理。然后在 -(void)animationDidStop:(CAAnimation *)anim finish:(BOOL)flag 回调中,我删除了覆盖层并关闭了菜单。这并不完全匹配默认的 NSMenu 的 flash,但我想要一个 37Signals/Stack Overflow 黄色淡出技术< /a>,所以它对我有用。这是代码:

-(void) mouseUp:(NSEvent *)theEvent {
    CALayer *layer = [CALayer layer];
    [layer setDelegate:self];
    [layer setBackgroundColor:CGColorCreateGenericRGB(0.0, 0.0, 1.0, 1.0)];

    selectionOverlayView = [[NSView alloc] init];
    [selectionOverlayView setWantsLayer:YES];
    [selectionOverlayView setFrame:self.frame];
    [selectionOverlayView setLayer:layer];
    [[selectionOverlayView layer] setNeedsDisplay];
    [selectionOverlayView setAlphaValue:0.0];
    [self addSubview:selectionOverlayView];

    CABasicAnimation *alphaAnimation1 = [CABasicAnimation animationWithKeyPath: @"alphaValue"];
    alphaAnimation1.beginTime = 0.0;
    alphaAnimation1.fromValue = [NSNumber numberWithFloat: 0.0];
    alphaAnimation1.toValue = [NSNumber numberWithFloat: 1.0];
    alphaAnimation1.duration = 0.07;

    CABasicAnimation *alphaAnimation2 = [CABasicAnimation animationWithKeyPath: @"alphaValue"];
    alphaAnimation2.beginTime = 0.07;
    alphaAnimation2.fromValue = [NSNumber numberWithFloat: 1.0];
    alphaAnimation2.toValue = [NSNumber numberWithFloat: 0.0];
    alphaAnimation2.duration = 0.07;

    CAAnimationGroup *selectionAnimation = [CAAnimationGroup animation];
    selectionAnimation.delegate = self;
    selectionAnimation.animations = [NSArray arrayWithObjects:alphaAnimation1, alphaAnimation2, nil];
    selectionAnimation.duration = 0.14;
    [selectionOverlayView setAnimations:[NSDictionary dictionaryWithObject:selectionAnimation forKey:@"frameOrigin"]];

    [[selectionOverlayView animator] setFrame:[selectionOverlayView frame]];
}

-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    [selectionOverlayView removeFromSuperview];

    NSMenuItem *enclosingMenuItem = [self enclosingMenuItem];
    NSMenu *enclosingMenu = [enclosingMenuItem menu];
    [enclosingMenu cancelTracking];
    [enclosingMenu performActionForItemAtIndex:[enclosingMenu indexOfItem:enclosingMenuItem]];
}

I know this is over a year old, but this was the first hit on my Google search and was unanswered, so I'm posting my answer for sake of those still looking for a solution.

For my app, I used Core Animation with a custom NSView for the NSMenuItem view. I created a new layer-backed view, set the background color, and added it to my custom view. I then animated the layer (the flashing part). Then in the -(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag callback, I removed the overlay and closed the menu. This doesn't perfectly match the default NSMenu's flash, but I wanted a 37Signals/Stack Overflow Yellow Fade Technique, so it works for me. Here it is in code:

-(void) mouseUp:(NSEvent *)theEvent {
    CALayer *layer = [CALayer layer];
    [layer setDelegate:self];
    [layer setBackgroundColor:CGColorCreateGenericRGB(0.0, 0.0, 1.0, 1.0)];

    selectionOverlayView = [[NSView alloc] init];
    [selectionOverlayView setWantsLayer:YES];
    [selectionOverlayView setFrame:self.frame];
    [selectionOverlayView setLayer:layer];
    [[selectionOverlayView layer] setNeedsDisplay];
    [selectionOverlayView setAlphaValue:0.0];
    [self addSubview:selectionOverlayView];

    CABasicAnimation *alphaAnimation1 = [CABasicAnimation animationWithKeyPath: @"alphaValue"];
    alphaAnimation1.beginTime = 0.0;
    alphaAnimation1.fromValue = [NSNumber numberWithFloat: 0.0];
    alphaAnimation1.toValue = [NSNumber numberWithFloat: 1.0];
    alphaAnimation1.duration = 0.07;

    CABasicAnimation *alphaAnimation2 = [CABasicAnimation animationWithKeyPath: @"alphaValue"];
    alphaAnimation2.beginTime = 0.07;
    alphaAnimation2.fromValue = [NSNumber numberWithFloat: 1.0];
    alphaAnimation2.toValue = [NSNumber numberWithFloat: 0.0];
    alphaAnimation2.duration = 0.07;

    CAAnimationGroup *selectionAnimation = [CAAnimationGroup animation];
    selectionAnimation.delegate = self;
    selectionAnimation.animations = [NSArray arrayWithObjects:alphaAnimation1, alphaAnimation2, nil];
    selectionAnimation.duration = 0.14;
    [selectionOverlayView setAnimations:[NSDictionary dictionaryWithObject:selectionAnimation forKey:@"frameOrigin"]];

    [[selectionOverlayView animator] setFrame:[selectionOverlayView frame]];
}

-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    [selectionOverlayView removeFromSuperview];

    NSMenuItem *enclosingMenuItem = [self enclosingMenuItem];
    NSMenu *enclosingMenu = [enclosingMenuItem menu];
    [enclosingMenu cancelTracking];
    [enclosingMenu performActionForItemAtIndex:[enclosingMenu indexOfItem:enclosingMenuItem]];
}
幸福不弃 2024-10-22 07:18:27

实际上可以让自定义视图像常规 NSMenuItem 一样闪烁,而无需手动实现动画。

注意:这使用了私有 API,并且还修复了一些与自定义视图相关的其他奇怪的 NSMenuItem 怪癖。

NSMenuItem.h

#import <AppKit/AppKit.h>

@interface NSMenuItem ()
    - (BOOL)_viewHandlesEvents;
@end

桥接标头

#import "NSMenuItem.h"

MenuItem.swift

class MenuItem: NSMenuItem {
    override func _viewHandlesEvents() -> Bool {
        return false
    }
}

这个 API 确实应该公开,如果您不是为 App Store 开发,那么它也许值得一看。

It is actually possible to have your custom view flash like a regular NSMenuItem without implementing the animation manually.

Note: this uses a private API and also fixes a handful of other strange NSMenuItem quirks related to custom views.

NSMenuItem.h

#import <AppKit/AppKit.h>

@interface NSMenuItem ()
    - (BOOL)_viewHandlesEvents;
@end

Bridging Header

#import "NSMenuItem.h"

MenuItem.swift

class MenuItem: NSMenuItem {
    override func _viewHandlesEvents() -> Bool {
        return false
    }
}

This API really ought to be public, and if you're not developing for the App Store, it might be worth having a look at.

泪是无色的血 2024-10-22 07:18:27

这是我的代码,它闪烁自定义菜单项。

int16_t   fireTimes;
BOOL      isSelected;

- (void)mouseEntered:(NSEvent*)event
{
    isSelected = YES;
}

- (void)mouseUp:(NSEvent*)event {

    fireTimes = 0;

    isSelected = !isSelected;
    [self setNeedsDisplay:YES];

    NSTimer *timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(animateDismiss:) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
}

-(void)animateDismiss:(NSTimer *)aTimer
{
    if (fireTimes <= 2) {
        isSelected = !isSelected;
        [self setNeedsDisplay:YES];
    } else {
        [aTimer invalidate];
        [self sendAction];
    }

    fireTimes++;
}

- (void)drawRect:(NSRect)dirtyRect {

    if (isSelected) {
        NSRect frame = NSInsetRect([self frame], -4.0f, -4.0f);
        [[NSColor selectedMenuItemColor] set];
        NSRectFill(frame);
        [itemNameFld setTextColor:[NSColor whiteColor]];
    } else {
        [itemNameFld setTextColor:[NSColor blackColor]];
    }

}

- (void)sendAction
{
    NSMenuItem *actualMenuItem = [self enclosingMenuItem];

    [NSApp sendAction:[actualMenuItem action] to:[actualMenuItem target] from:actualMenuItem];

    NSMenu *menu = [actualMenuItem menu];
    [menu cancelTracking];

    //  [self setNeedsDisplay:YES]; // I'm not sure of this
}

Here is my code that flashes a custom menu item.

int16_t   fireTimes;
BOOL      isSelected;

- (void)mouseEntered:(NSEvent*)event
{
    isSelected = YES;
}

- (void)mouseUp:(NSEvent*)event {

    fireTimes = 0;

    isSelected = !isSelected;
    [self setNeedsDisplay:YES];

    NSTimer *timer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(animateDismiss:) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
}

-(void)animateDismiss:(NSTimer *)aTimer
{
    if (fireTimes <= 2) {
        isSelected = !isSelected;
        [self setNeedsDisplay:YES];
    } else {
        [aTimer invalidate];
        [self sendAction];
    }

    fireTimes++;
}

- (void)drawRect:(NSRect)dirtyRect {

    if (isSelected) {
        NSRect frame = NSInsetRect([self frame], -4.0f, -4.0f);
        [[NSColor selectedMenuItemColor] set];
        NSRectFill(frame);
        [itemNameFld setTextColor:[NSColor whiteColor]];
    } else {
        [itemNameFld setTextColor:[NSColor blackColor]];
    }

}

- (void)sendAction
{
    NSMenuItem *actualMenuItem = [self enclosingMenuItem];

    [NSApp sendAction:[actualMenuItem action] to:[actualMenuItem target] from:actualMenuItem];

    NSMenu *menu = [actualMenuItem menu];
    [menu cancelTracking];

    //  [self setNeedsDisplay:YES]; // I'm not sure of this
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文