Cocoa / CoreGraphics / Quartz - 无边框 Quicktime X 窗口,具有圆边

发布于 2024-12-20 06:23:36 字数 499 浏览 2 评论 0原文

我正在为 Mac OS X 开发一个基于文档的应用程序。它是一种媒体播放器,但它不是播放音频或视频文件,而是打开包含指定 OpenGL 动画的元数据的文本文件。我想模仿苹果的 QuickTime X 窗口风格。这意味着,我必须自己绘制所有窗口,因为 Cocoa 没有合适的窗口样式。

有一件事情让我头疼:Mac OS X 窗口上通常会出现圆角。我尝试使用无边框窗口蒙版并使用一些 CGS 魔法 - 有一些允许窗口整形的私有 Apple 标头,但它们当然没有记录。我能够在窗户边缘切出矩形孔,但我不明白苹果是如何实现圆角的。

创建透明窗口并自己绘制框架是行不通的,因为 OpenGL 视口始终是矩形,更改它的唯一方法是打开 NSOpenGLCPSurfaceOpacity 以实现 alpha 透明度并使用模板缓冲区或着色器来剪切边缘,这看起来开销很大。

如果我将 OpenGLView 放入带有标题栏的标准 Cocoa 窗口中,则底部边缘是圆形的。这似乎发生在视图层次结构的 NSThemeFrame 阶段。有什么想法如何做到这一点吗?

I am developing a document based application for Mac OS X. It's a kind of media player, but instead of playing audio or video files it is supposed to open text-files containing meta-data specifying OpenGL animations. I would like to mimic Apples QuickTime X window style. This means, i have to do all the window drawings myself, because Cocoa has no appropriate window style.

There is one thing which gives me headaches: The rounded corners usually to be found on Mac OS X windows. I tried using the borderless window mask and working some CGS magic - there are some private Apple headers which allow window shaping, but they are of course undocumented. I was able to cut rectangular holes in my windows edges, but i couldn't figure out how Apple achieves rounded corners.

Creating a transparent window and drawing the frame myself does not work, because an OpenGL viewport is always rectangular, and the only way to change it is to turn on NSOpenGLCPSurfaceOpacity for alpha transparency and using the stencil buffer or shaders to cut out the edges, which seems like a hell of a lot of overhead.

If i put an OpenGLView into a standard Cocoa window with titlebar, the bottom edges are rounded. It seems this is happening at the NSThemeFrame stage of the view hierarchy. Any ideas how this is done?

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

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

发布评论

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

评论(3

佼人 2024-12-27 06:23:36

使用图层支持的视图,并在不可见窗口的 CALayer 中进行绘图。图层包括圆角和边框的自动处理。


CALayer 的背景位于 核心动画编程指南。要为NSView创建图层,您需要调用[view setWantsLayer:YES]。您将创建一个CAOpenGLLayer并使用setLayer:将其分配给视图。

请参阅 CALayerEssentials 获取演示如何使用 < code>CAOpenGLLayer 以及其他图层类型。

Use a layer-backed view, and do your drawing in the CALayer on an invisible window. Layers include automatic handling of rounded corners and borders.


Background for CALayer is in the Core Animation Programming Guide. To create a layer for NSView, you need to call [view setWantsLayer:YES]. You would create a CAOpenGLLayer and assign it to the view using setLayer:.

See CALayerEssentials for sample code demonstrating how to use CAOpenGLLayer among other layer types.

下壹個目標 2024-12-27 06:23:36

这是对在 <= 10.9 上获得圆角所需的分析。请注意,从 10.10+ 开始,您拥有 NSFullSizeContentViewWindowMask 和透明标题栏属性,这使事情变得简单。

假设您想要重新创建 QuickTime X 窗口。您已经有了一个 OpenGL 视图,在其中渲染了一些漂亮的茶壶,但是那个该死的标题栏一直妨碍您。您认为这是一件容易的事情,但是天哪,您错了吗:

问题 1)在 10.12 版本之前的 osx 上,opengl 视图是它们自己的表面。如果您曾经查看过 NSOpenGLView 的描述,您会发现其中有一行奇怪的内容:它不接受任何子视图。如果你尝试一下,你会发现 openglview 基本上会破坏一切。实际上,opengl 视图就像它自己的迷你窗口(它有自己的表面),它始终位于下面所有内容的“顶部”。所以这意味着抛开圆角,你将无法让系统在其上面绘制菜单图标,而你自己处理它们又繁琐又丑陋(例如悬停效果等)

现在还有2你可以混合 opengl 但在顶部有标题的方法。首先是通过使用图层。事实上,如果你将根视图设置为分层,那么 NSOpenGLView 也会自动变为分层。然后一切就神奇地起作用了:你得到圆角,并且可以在顶部渲染你的关闭按钮。请注意,要执行此操作,您必须转义内容视图,并将您自己的视图插入为 NSThemeFrame 的第一个子视图。由于红绿灯图标将以同级顺序出现在插入的视图之后,因此它们将合成在视图的顶部。因为事物是由层支持的,所以这实际上是有效的。一个例子如下所示:

#import <Cocoa/Cocoa.h>
#import <OpenGL/gl.h>
#import <OpenGL/glu.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGL/gl3.h>
#include <mach/mach_time.h>
#import <Cocoa/Cocoa.h>

@class FOTWindowFrame;

/**
 View responsible for title bar drawing.
 */
@interface FOTWindowTitle : NSView

@end

/**
 View acting as the window's frame and responsible for mouse tracking.
 */
@interface FOTWindowFrame : NSView

@end

/**
 NSWindow subclass to make auto hiding and showing a window's title bar (a la QuickTime X) easier.
 */
@interface FOTWindow : NSWindow

@property (nonatomic, strong) FOTWindowFrame *fullContentView;

/**
 Adds a subview that stays below the title bar.

 @warning All views in this window will get wantsLayer = YES.
 */
- (void)addSubviewBelowTitleBar:(NSView *)subview;

/**
 The alpha value the title bar view will be set to when it fades in.
 */
@property CGFloat titleBarFadeInAlphaValue;

/**
 The alpha value the title bar view will be set to when it fades out.
 */
@property CGFloat titleBarFadeOutAlphaValue;

/**
 Block used to specify custom drawing code for a window's title bar.
 @param drawsAsMainWindow Whether the window is in its main state.
 @param drawingRect The entire drawing area of the title bar.
 @param clippingPath Path with the shape of the window title bar's rounded corners (can be used to clip to).
 */
typedef void (^FOTWindowTitleBarDrawingBlock)(BOOL drawsAsMainWindow, NSRect drawingRect, NSBezierPath *clippingPath);

/**
 Specify custom title bar background drawing code. If none is specified, the standard OS X title bar background will be drawn.
 */
@property (nonatomic, strong) FOTWindowTitleBarDrawingBlock titleBarDrawingBlock;

/**
 Block used to specify custom drawing code for a window's title.
 @param drawsAsMainWindow Whether the window is in its main state.
 @param drawingRect The entire drawing area of the title bar.
 */
typedef void (^FOTWindowTitleDrawingBlock)(BOOL drawsAsMainWindow, NSRect titleBarRect);

/**
 Specify custom title text drawing code. If none is specified, the standard OS X-style window title will be drawn.
 */
@property (nonatomic, strong) FOTWindowTitleDrawingBlock titleDrawingBlock;

@end


#define kTitleBarHeight 400.0

@implementation FOTWindowTitle

// Draws the title bar background and window title
- (void)drawRect:(NSRect)dirtyRect
{
    FOTWindow* window = (FOTWindow*)self.window;
    
    CGFloat roundedRectangleCornerRadius = 4;
    NSRect roundedRectangleRect = NSMakeRect(0, 0, NSWidth(self.frame), kTitleBarHeight);
    NSRect roundedRectangleInnerRect = NSInsetRect(roundedRectangleRect, roundedRectangleCornerRadius, roundedRectangleCornerRadius);
    NSBezierPath* clippingPath = [NSBezierPath bezierPath];
    [clippingPath moveToPoint: NSMakePoint(NSMinX(roundedRectangleRect), NSMinY(roundedRectangleRect))];
    [clippingPath lineToPoint: NSMakePoint(NSMaxX(roundedRectangleRect), NSMinY(roundedRectangleRect))];
    [clippingPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(roundedRectangleInnerRect), NSMaxY(roundedRectangleInnerRect)) radius: roundedRectangleCornerRadius startAngle: 0 endAngle: 90];
    [clippingPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(roundedRectangleInnerRect), NSMaxY(roundedRectangleInnerRect)) radius: roundedRectangleCornerRadius startAngle: 90 endAngle: 180];
    [clippingPath closePath];
    
    BOOL drawsAsMainWindow = (window.isMainWindow && [NSApplication sharedApplication].isActive);
    NSRect drawingRect = NSMakeRect(0, 0, NSWidth(self.frame), kTitleBarHeight);
    
    if (window.titleBarDrawingBlock) {
        // Draw custom titlebar background
        window.titleBarDrawingBlock(drawsAsMainWindow, drawingRect, clippingPath);
    } else {
        // Draw default titlebar background
        NSGradient* gradient;
        if (drawsAsMainWindow) {
            gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.66 alpha:1.0] endingColor:[NSColor colorWithDeviceWhite:0.9 alpha:1.0]];
        } else {
            gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.878 alpha:1.0] endingColor:[NSColor colorWithDeviceWhite:0.976 alpha:1.0]];
        }
        
        [gradient drawInBezierPath:clippingPath angle:90];
        
        // 1px line
        NSRect shadowRect = NSMakeRect(0, 0, NSWidth(self.frame), 1);
        [[NSColor colorWithDeviceWhite:(drawsAsMainWindow)? 0.408 : 0.655 alpha:1.0] set];
        NSRectFill(shadowRect);
    }
    
    if (window.titleDrawingBlock) {
        // Draw custom title
        window.titleDrawingBlock(drawsAsMainWindow, drawingRect);
    } else {
        // Draw default title
        
        // Rect
        NSRect textRect;
        if (window.representedURL) {
            textRect = NSMakeRect(20, -2, NSWidth(self.frame)-20, NSHeight(self.frame));
        } else {
            textRect = NSMakeRect(0, -2, NSWidth(self.frame), NSHeight(self.frame));
        }
        
        // Paragraph style
        NSMutableParagraphStyle* textStyle = [NSMutableParagraphStyle defaultParagraphStyle].mutableCopy;
        [textStyle setAlignment: NSCenterTextAlignment];
        
        // Shadow
        NSShadow* titleTextShadow = [[NSShadow alloc] init];
        titleTextShadow.shadowBlurRadius = 0.0;
        titleTextShadow.shadowOffset = NSMakeSize(0, -1);
        titleTextShadow.shadowColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.5];
        
        NSColor *textColor = [NSColor colorWithDeviceWhite:56.0/255.0 alpha:drawsAsMainWindow? 1.0 : 0.5];
        
        // Draw the title
        NSDictionary* textFontAttributes = @{NSFontAttributeName: [NSFont titleBarFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]],
                                             NSForegroundColorAttributeName: textColor,
                                             NSParagraphStyleAttributeName: textStyle,
                                             NSShadowAttributeName: titleTextShadow};
        
        [window.title drawInRect: textRect withAttributes: textFontAttributes];
    }
}

@end

#pragma mark -

@interface FOTWindowFrame ()

@property (strong) FOTWindowTitle *titleBar;

@end

@implementation FOTWindowFrame

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

- (BOOL)canBecomeKeyWindow {
    return YES;
}

- (id)initWithFrame:(NSRect)frameRect andContent:(NSView*)view
{
    self = [super initWithFrame:frameRect];
    
    if (self) {
        view.frame = frameRect;
    
        _titleBar = view;
        [_titleBar setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin|NSViewMinXMargin];
//      [_titleBar setAlphaValue:0];
        [self addSubview:_titleBar];
    }
    
    return self;
}

- (void)updateTrackingAreas
{
    [super updateTrackingAreas];
    
    [self addTrackingArea:[[NSTrackingArea alloc] initWithRect:self.frame options:NSTrackingActiveAlways|NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited owner:self userInfo:nil]];
}

- (void)mouseEntered:(NSEvent *)theEvent
{
    FOTWindow* window = (FOTWindow*)self.window;
    [[window standardWindowButton:NSWindowCloseButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowZoomButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowMiniaturizeButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowFullScreenButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:window.titleBarFadeInAlphaValue];
//  [_titleBar.animator setAlphaValue:window.titleBarFadeInAlphaValue];
}

- (void)mouseExited:(NSEvent *)theEvent
{
    FOTWindow* window = (FOTWindow*)self.window;
    [[window standardWindowButton:NSWindowCloseButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowZoomButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowMiniaturizeButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowFullScreenButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:window.titleBarFadeOutAlphaValue];
//  [_titleBar.animator setAlphaValue:window.titleBarFadeOutAlphaValue];
}

- (void)drawRect:(NSRect)dirtyRect
{
    [self.window.backgroundColor set];
    NSRectFillUsingOperation(dirtyRect, NSCompositeCopy);
}

@end

#pragma mark -

@implementation FOTWindow
{
    NSView *_originalThemeFrame;
}

- (BOOL)canBecomeKeyWindow {
    return YES;
}

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
    self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:NO];
    
    if (self) {
        _titleBarFadeInAlphaValue = 1.0;
        _titleBarFadeOutAlphaValue = 0.0;
        
        _originalThemeFrame = [self.contentView superview];
        _originalThemeFrame.wantsLayer = YES;
    }
    
    return self;
}

-(void) setContent: (NSView *) view {
    
    _fullContentView = [[FOTWindowFrame alloc] initWithFrame:self.frame andContent:view];
//  _fullContentView.wantsLayer = YES;
    [_originalThemeFrame addSubview:_fullContentView positioned:NSWindowBelow relativeTo:_originalThemeFrame.subviews[0]];
    
    [[self standardWindowButton:NSWindowCloseButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    [[self standardWindowButton:NSWindowZoomButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    [[self standardWindowButton:NSWindowMiniaturizeButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    
    [_fullContentView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
    [_fullContentView setFrame:_originalThemeFrame.frame];
}

- (void)awakeFromNib
{
    [[self standardWindowButton:NSWindowFullScreenButton] setAlphaValue:_titleBarFadeOutAlphaValue];
}

- (void)becomeKeyWindow
{
    [super becomeKeyWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)resignKeyWindow
{
    [super resignKeyWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)becomeMainWindow
{
    [super becomeMainWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)resignMainWindow
{
    [super resignMainWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)setTitle:(NSString *)aString
{
    [super setTitle:aString];
    
    [self.fullContentView setNeedsDisplay:YES];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)setRepresentedURL:(NSURL *)url
{
    [super setRepresentedURL:url];
    
    //Match the document icon button to the alpha value of the close button (and the other buttons, essentially)
    [[self standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:[self standardWindowButton:NSWindowCloseButton].alphaValue];
    
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)addSubviewBelowTitleBar:(NSView *)subview
{
    [_fullContentView addSubview:subview positioned:NSWindowBelow relativeTo:_fullContentView.titleBar];
}

@end

/******* */
@interface MyOpenGLView : NSOpenGLView
@end

@implementation MyOpenGLView

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

+ (BOOL)canDrawOpenGL {
    return YES;
}

- (void)prepareOpenGL {
    // Set up OpenGL context and other properties
    [super prepareOpenGL];
    [[self openGLContext] makeCurrentContext];
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glFinish();
}

- (void)drawRect:(NSRect)dirtyRect {
    
    static float i = 0.5;
    i += 0.01;
    glClearColor(0.0f, i, 0.0f, 1.0f);
    
    glClear(GL_COLOR_BUFFER_BIT);
    // Swap the buffers to display the rendered image
//  [[self openGLContext] flushBuffer];
    glFlush();
}

@end

int x = 0;
MyOpenGLView *glView;

CVReturn MyDisplayCallback(CVDisplayLinkRef displayLink,
    const CVTimeStamp *inTime,
    const CVTimeStamp *outTime,
    CVOptionFlags flagsIn,
    CVOptionFlags *flagsOut,
    void *displayLinkContext) {
        [glView prepareOpenGL];
        
        for (int i = 0; i < 20; i++ ){
            printf("-------\n");
            CVTimeStamp out;
            CVDisplayLinkGetCurrentTime(displayLink, &out);
            printf("Bef: %llu\n", out.videoTime);
            [glView drawRect:NSZeroRect];
            CVDisplayLinkGetCurrentTime(displayLink, &out);
            printf("Aft: %llu\n", out.videoTime);
        }
        CVDisplayLinkStop(displayLink);
        return kCVReturnSuccess;
}


int main(int argc, const char * argv[]) {
    // Set up the application and window
    @autoreleasepool {
        NSApplication *app = [NSApplication sharedApplication];
        [app setActivationPolicy:NSApplicationActivationPolicyRegular];

        FOTWindow *window = [[FOTWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 400)
            styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
            backing:NSBackingStoreBuffered
            defer:NO];
//      [window setBackingType:NSBackingStoreBuffered]; // New backing store is type 3, unbuffered
        [window setShowsResizeIndicator:YES];
        
        // Create an NSOpenGLPixelFormat object with double buffering enabled
        NSOpenGLPixelFormatAttribute attribs[] = {
//          NSOpenGLPFATripleBuffer,
            NSOpenGLPFAAccelerated,
            0
        };
        NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
        
        // Create an NSOpenGLContext object with the pixel format
        NSOpenGLContext *glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
        
        // Set the pixel format and context for the view
        glView = [[MyOpenGLView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400) pixelFormat:pixelFormat];
        [glView setOpenGLContext:glContext];
    
//      window.contentView = glView;
        [window setContent: glView];
        window.movableByWindowBackground = YES;
//      [window setContentView: glView];
        
        // Optionally see what happens when using a CAOpenGLLayer instance
    
      [glView setWantsLayer: NO]; // wantsLayer is actually forced
//      [glView.layer setBackgroundColor:[NSColor blueColor].CGColor];
        
        
        // Set up the window and run the application
        
//      NSView* frameView = [[window contentView] superview];
//      [glView setFrame:[frameView bounds]];
//      [frameView addSubview:glView];



//      window.contentView = glView;
        [window makeFirstResponder:glView];
        [window makeKeyAndOrderFront:nil];
    
        
        GLint a = 1;
        [glContext setValues:&a forParameter:NSOpenGLCPSwapInterval];
        
        CVDisplayLinkRef _link;
        CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(),&_link);
        
        
        CVDisplayLinkSetOutputCallback(_link, MyDisplayCallback, NULL);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            printf("Starting\n");
            CVDisplayLinkStart(_link);

        });
        
        [app run];
        
    }
    return 0;
}

现在 nsopengllayer 的缺点当然是所有渲染都必须在 drawRect 方法中完成,这与 openglview 不同,在 openglview 中,您可以在 drawRect 之外获取上下文并随意绘制。如果您必须使用非图层支持的视图,仍然有办法。有一个晦涩的 NSOpenGLCPSurfaceOrder 属性,如果设置为 -1,苹果会说“如果值为 –1,则顺序低于窗口”。这并没有多大意义除非你考虑到我之前提到的 opengl 视图是它们自己的表面,就像它们自己的“窗口”一样,所以这实际上相当于将一个窗口放在另一个窗口下面(这可能是层支持视图存在之前的产物,所以这是)。一个巧妙的技巧可以有效地给予两层重用合成器机制)现在要实际利用它,顶部的窗口(创建的窗口)必须是透明的,并允许下面的内容透过,这就是。你这样做:你必须将不透明度设置为“否”,设置透明背景颜色,并在关联的 nsopenglview 中绘制一个清晰的矩形,以允许下面的内容通过 NSWindowFrame 进行组合。通过上面的技巧,您可以实现类似的效果,即直接在内容顶部放置带有圆角的闭合图标。

所有这一切的缺点是,正如您所指出的,非不透明的 nswindow 在调整大小时非常慢,其他人也注意到了这一点(https://lists.apple.com/archives/cocoa-dev/2012/Nov/msg00004.htmlhttps://lists.apple.com/archives/cocoa-dev/2012/Feb/msg00667.html)。还有另一种选择,您可以采用传统的 nsopenglview (不透明窗口,位于顶部表面)并在其顶部创建另一个透明窗口来容纳您的控件。这个透明窗口要小得多,只需要标题栏大小,因此它不应该遭受相同的性能损失。您可以在此处查看此方法的示例:https://github.com/insidegui/GRGlassWindow

现在回到圆角:如果您实际为透明 openglview 运行上面的代码,您会发现底角是圆角的,但顶角不是。可以通过不在 nsopenglview 中绘制完整的透明矩形,而只绘制圆角矩形蒙版来解决此问题,我将其作为练习。

总而言之,通过图层支持的视图(与 NSThemeFrame 技巧结合使用),您可以通过 OpenGL 和常规石英绘图免费获得顶角和底角。

对于非图层支持视图,底角是“免费”的,顶角实际上也是圆形的,但您需要注意不要在它们上面绘制(与底角不同,它们似乎不会自动剪切) 。如果您使用石英绘图,则需要在绘图时遮盖角落。使用不透明的 nswindow 和“顶部”gl 表面,您必须创建一个具有透明度的 opengl 表面,这样您就不会在角落上绘制。对于“底部”gl 表面,您必须使整个窗口透明(非不透明),并创建适当的遮罩以让下面的内容透过。

但对我来说,真正有趣的是,即使您绘制了一个非分层的不透明全矩形,底角也是圆角的,这意味着圆角底角会根据需要自动剪切/遮盖,而顶角则没有这样做。我非常努力地追踪这是如何发生的,它似乎作为 NSThemeFrame 初始化的一部分,它进行了以下调用:

-[NSThemeFrame _maskCorners:clipRect:]
-[NSThemeFrame drawOverlayRect:] + 569
-[NSView _drawOverlayRectSet:] + 298
-[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 3138
-[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 841
-[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 314
-[NSView _displayRectIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:] + 2828
-[NSView displayIfNeeded] + 1680

其中签名是 - (void)_maskCorners:(NSUInteger)aFlags ClipRect:( NSRect)aRectaFlags 是一个位掩码,其低两位确定是否希望底角/顶角为圆角。 (这里的 rect 似乎并不重要,我什至将其替换为 0,但没有改变效果)。您甚至可以自己调配(或子类 NSThemeFrame 并覆盖)此方法,并看到如果将 _maskCorners 设置为 0,则圆底角会消失。通常,仅使用 1(底角)的值来调用此函数,但反汇编表明它也应该处理顶角的 0b10 值。那么我们是否可以通过调整它来始终传递 0b11 并获得圆角的顶角和底角?

我真的希望它能奏效,但看来我们运气不好,这并不那么容易奏效。我什至非常努力地想弄清楚到底发生了什么:内部 _maskCorners 调用 CoreUI 框架 CUIRenderer::Draw,最终调用 CUIArtFileRenderer: :DrawWindowFrameStandard。有 4 个特定的 CUI 图像 ID:0x4e79、0x4e7b、0x4e56、0x4e58,它们对应于 4 个圆角的图像 ID 掩码。我什至能够提取 CGImageRef 等效项,实际上它们是圆角蒙版(4e56 用于左下角,4e58 用于右下角)。它获取这些图像并调用CGContextSetCompositeOperation,然后调用CGContextDrawImages。不过,所有 4 个角的代码本质上是相同的,因此令人惊讶的是,由于某种原因,这似乎不适用于顶角。也许位置的计算是错误的,并且从未被注意到,因为它从未被执行过,或者窗口服务器本身可能参与了这个阴谋,而圆底角只能在它的帮助下工作(可能与“允许窗口整形的Apple标头”有关)如果你记得这些是什么,我想知道。)

如果有人好奇,可以尝试的一件事是通过手动调用来复制 DrawWindowFrameStandard 所做的事情 。 CGContextDrawImage 并查看是否能够遮盖窗口的部分内容。不知道这实际上是如何与 opengl 内容交互的,尽管看起来它必须如此。

This is an analysis of what it takes to get rounded corners on <= 10.9. Note that from 10.10+ you have NSFullSizeContentViewWindowMask and transparent titlebar properties which make things easy.

So let's say you want to recreate the QuickTime X window. You've got an OpenGL view where you've rendered some beautiful teapots, but that darn titlebar keeps getting in the way. You think it'd be an easy thing, but boy are you mistaken:

Issue 1) On pre-10.12-ish osx, opengl views are their own surface. If you ever looked at the description for NSOpenGLView, it has a curious line that it doesn't accept any subviews. If you try it, you'll find that an openglview basically clobbers everything. Effectively an opengl view is like its own mini window (it has its own surface) which is always "on top" of everything underneath. So this means that setting aside the rounded corners, you won't be able to let the system draw the menu icons on top of it, and handling them yourself is tedious and ugly (e.g. hover effects, etc.)

Now there are still 2 ways you can mix opengl but have titles on top. First is via use of layers. In fact if you set the root view to be layered back, then NSOpenGLView automatically becomes layer backed as well. And then everything just magically works: you get rounded corners, and can render your close buttons on top. Note that to do this you have to escape the content view and essentially insert your own view as the first child of NSThemeFrame. Because the stoplight icons will come after your inserted view in the sibling order, they will be composited on top of your view. And because things are layer backed, this actually works. An example is shown below:

#import <Cocoa/Cocoa.h>
#import <OpenGL/gl.h>
#import <OpenGL/glu.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGL/gl3.h>
#include <mach/mach_time.h>
#import <Cocoa/Cocoa.h>

@class FOTWindowFrame;

/**
 View responsible for title bar drawing.
 */
@interface FOTWindowTitle : NSView

@end

/**
 View acting as the window's frame and responsible for mouse tracking.
 */
@interface FOTWindowFrame : NSView

@end

/**
 NSWindow subclass to make auto hiding and showing a window's title bar (a la QuickTime X) easier.
 */
@interface FOTWindow : NSWindow

@property (nonatomic, strong) FOTWindowFrame *fullContentView;

/**
 Adds a subview that stays below the title bar.

 @warning All views in this window will get wantsLayer = YES.
 */
- (void)addSubviewBelowTitleBar:(NSView *)subview;

/**
 The alpha value the title bar view will be set to when it fades in.
 */
@property CGFloat titleBarFadeInAlphaValue;

/**
 The alpha value the title bar view will be set to when it fades out.
 */
@property CGFloat titleBarFadeOutAlphaValue;

/**
 Block used to specify custom drawing code for a window's title bar.
 @param drawsAsMainWindow Whether the window is in its main state.
 @param drawingRect The entire drawing area of the title bar.
 @param clippingPath Path with the shape of the window title bar's rounded corners (can be used to clip to).
 */
typedef void (^FOTWindowTitleBarDrawingBlock)(BOOL drawsAsMainWindow, NSRect drawingRect, NSBezierPath *clippingPath);

/**
 Specify custom title bar background drawing code. If none is specified, the standard OS X title bar background will be drawn.
 */
@property (nonatomic, strong) FOTWindowTitleBarDrawingBlock titleBarDrawingBlock;

/**
 Block used to specify custom drawing code for a window's title.
 @param drawsAsMainWindow Whether the window is in its main state.
 @param drawingRect The entire drawing area of the title bar.
 */
typedef void (^FOTWindowTitleDrawingBlock)(BOOL drawsAsMainWindow, NSRect titleBarRect);

/**
 Specify custom title text drawing code. If none is specified, the standard OS X-style window title will be drawn.
 */
@property (nonatomic, strong) FOTWindowTitleDrawingBlock titleDrawingBlock;

@end


#define kTitleBarHeight 400.0

@implementation FOTWindowTitle

// Draws the title bar background and window title
- (void)drawRect:(NSRect)dirtyRect
{
    FOTWindow* window = (FOTWindow*)self.window;
    
    CGFloat roundedRectangleCornerRadius = 4;
    NSRect roundedRectangleRect = NSMakeRect(0, 0, NSWidth(self.frame), kTitleBarHeight);
    NSRect roundedRectangleInnerRect = NSInsetRect(roundedRectangleRect, roundedRectangleCornerRadius, roundedRectangleCornerRadius);
    NSBezierPath* clippingPath = [NSBezierPath bezierPath];
    [clippingPath moveToPoint: NSMakePoint(NSMinX(roundedRectangleRect), NSMinY(roundedRectangleRect))];
    [clippingPath lineToPoint: NSMakePoint(NSMaxX(roundedRectangleRect), NSMinY(roundedRectangleRect))];
    [clippingPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMaxX(roundedRectangleInnerRect), NSMaxY(roundedRectangleInnerRect)) radius: roundedRectangleCornerRadius startAngle: 0 endAngle: 90];
    [clippingPath appendBezierPathWithArcWithCenter: NSMakePoint(NSMinX(roundedRectangleInnerRect), NSMaxY(roundedRectangleInnerRect)) radius: roundedRectangleCornerRadius startAngle: 90 endAngle: 180];
    [clippingPath closePath];
    
    BOOL drawsAsMainWindow = (window.isMainWindow && [NSApplication sharedApplication].isActive);
    NSRect drawingRect = NSMakeRect(0, 0, NSWidth(self.frame), kTitleBarHeight);
    
    if (window.titleBarDrawingBlock) {
        // Draw custom titlebar background
        window.titleBarDrawingBlock(drawsAsMainWindow, drawingRect, clippingPath);
    } else {
        // Draw default titlebar background
        NSGradient* gradient;
        if (drawsAsMainWindow) {
            gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.66 alpha:1.0] endingColor:[NSColor colorWithDeviceWhite:0.9 alpha:1.0]];
        } else {
            gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.878 alpha:1.0] endingColor:[NSColor colorWithDeviceWhite:0.976 alpha:1.0]];
        }
        
        [gradient drawInBezierPath:clippingPath angle:90];
        
        // 1px line
        NSRect shadowRect = NSMakeRect(0, 0, NSWidth(self.frame), 1);
        [[NSColor colorWithDeviceWhite:(drawsAsMainWindow)? 0.408 : 0.655 alpha:1.0] set];
        NSRectFill(shadowRect);
    }
    
    if (window.titleDrawingBlock) {
        // Draw custom title
        window.titleDrawingBlock(drawsAsMainWindow, drawingRect);
    } else {
        // Draw default title
        
        // Rect
        NSRect textRect;
        if (window.representedURL) {
            textRect = NSMakeRect(20, -2, NSWidth(self.frame)-20, NSHeight(self.frame));
        } else {
            textRect = NSMakeRect(0, -2, NSWidth(self.frame), NSHeight(self.frame));
        }
        
        // Paragraph style
        NSMutableParagraphStyle* textStyle = [NSMutableParagraphStyle defaultParagraphStyle].mutableCopy;
        [textStyle setAlignment: NSCenterTextAlignment];
        
        // Shadow
        NSShadow* titleTextShadow = [[NSShadow alloc] init];
        titleTextShadow.shadowBlurRadius = 0.0;
        titleTextShadow.shadowOffset = NSMakeSize(0, -1);
        titleTextShadow.shadowColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.5];
        
        NSColor *textColor = [NSColor colorWithDeviceWhite:56.0/255.0 alpha:drawsAsMainWindow? 1.0 : 0.5];
        
        // Draw the title
        NSDictionary* textFontAttributes = @{NSFontAttributeName: [NSFont titleBarFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]],
                                             NSForegroundColorAttributeName: textColor,
                                             NSParagraphStyleAttributeName: textStyle,
                                             NSShadowAttributeName: titleTextShadow};
        
        [window.title drawInRect: textRect withAttributes: textFontAttributes];
    }
}

@end

#pragma mark -

@interface FOTWindowFrame ()

@property (strong) FOTWindowTitle *titleBar;

@end

@implementation FOTWindowFrame

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

- (BOOL)canBecomeKeyWindow {
    return YES;
}

- (id)initWithFrame:(NSRect)frameRect andContent:(NSView*)view
{
    self = [super initWithFrame:frameRect];
    
    if (self) {
        view.frame = frameRect;
    
        _titleBar = view;
        [_titleBar setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin|NSViewMinXMargin];
//      [_titleBar setAlphaValue:0];
        [self addSubview:_titleBar];
    }
    
    return self;
}

- (void)updateTrackingAreas
{
    [super updateTrackingAreas];
    
    [self addTrackingArea:[[NSTrackingArea alloc] initWithRect:self.frame options:NSTrackingActiveAlways|NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited owner:self userInfo:nil]];
}

- (void)mouseEntered:(NSEvent *)theEvent
{
    FOTWindow* window = (FOTWindow*)self.window;
    [[window standardWindowButton:NSWindowCloseButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowZoomButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowMiniaturizeButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowFullScreenButton].animator setAlphaValue:window.titleBarFadeInAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:window.titleBarFadeInAlphaValue];
//  [_titleBar.animator setAlphaValue:window.titleBarFadeInAlphaValue];
}

- (void)mouseExited:(NSEvent *)theEvent
{
    FOTWindow* window = (FOTWindow*)self.window;
    [[window standardWindowButton:NSWindowCloseButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowZoomButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowMiniaturizeButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowFullScreenButton].animator setAlphaValue:window.titleBarFadeOutAlphaValue];
    [[window standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:window.titleBarFadeOutAlphaValue];
//  [_titleBar.animator setAlphaValue:window.titleBarFadeOutAlphaValue];
}

- (void)drawRect:(NSRect)dirtyRect
{
    [self.window.backgroundColor set];
    NSRectFillUsingOperation(dirtyRect, NSCompositeCopy);
}

@end

#pragma mark -

@implementation FOTWindow
{
    NSView *_originalThemeFrame;
}

- (BOOL)canBecomeKeyWindow {
    return YES;
}

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
    self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:NO];
    
    if (self) {
        _titleBarFadeInAlphaValue = 1.0;
        _titleBarFadeOutAlphaValue = 0.0;
        
        _originalThemeFrame = [self.contentView superview];
        _originalThemeFrame.wantsLayer = YES;
    }
    
    return self;
}

-(void) setContent: (NSView *) view {
    
    _fullContentView = [[FOTWindowFrame alloc] initWithFrame:self.frame andContent:view];
//  _fullContentView.wantsLayer = YES;
    [_originalThemeFrame addSubview:_fullContentView positioned:NSWindowBelow relativeTo:_originalThemeFrame.subviews[0]];
    
    [[self standardWindowButton:NSWindowCloseButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    [[self standardWindowButton:NSWindowZoomButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    [[self standardWindowButton:NSWindowMiniaturizeButton] setAlphaValue:_titleBarFadeOutAlphaValue];
    
    [_fullContentView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
    [_fullContentView setFrame:_originalThemeFrame.frame];
}

- (void)awakeFromNib
{
    [[self standardWindowButton:NSWindowFullScreenButton] setAlphaValue:_titleBarFadeOutAlphaValue];
}

- (void)becomeKeyWindow
{
    [super becomeKeyWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)resignKeyWindow
{
    [super resignKeyWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)becomeMainWindow
{
    [super becomeMainWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)resignMainWindow
{
    [super resignMainWindow];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)setTitle:(NSString *)aString
{
    [super setTitle:aString];
    
    [self.fullContentView setNeedsDisplay:YES];
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)setRepresentedURL:(NSURL *)url
{
    [super setRepresentedURL:url];
    
    //Match the document icon button to the alpha value of the close button (and the other buttons, essentially)
    [[self standardWindowButton:NSWindowDocumentIconButton] setAlphaValue:[self standardWindowButton:NSWindowCloseButton].alphaValue];
    
    [_fullContentView.titleBar setNeedsDisplay:YES];
}

- (void)addSubviewBelowTitleBar:(NSView *)subview
{
    [_fullContentView addSubview:subview positioned:NSWindowBelow relativeTo:_fullContentView.titleBar];
}

@end

/******* */
@interface MyOpenGLView : NSOpenGLView
@end

@implementation MyOpenGLView

- (BOOL) mouseDownCanMoveWindow {
    return YES;
}

+ (BOOL)canDrawOpenGL {
    return YES;
}

- (void)prepareOpenGL {
    // Set up OpenGL context and other properties
    [super prepareOpenGL];
    [[self openGLContext] makeCurrentContext];
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glFinish();
}

- (void)drawRect:(NSRect)dirtyRect {
    
    static float i = 0.5;
    i += 0.01;
    glClearColor(0.0f, i, 0.0f, 1.0f);
    
    glClear(GL_COLOR_BUFFER_BIT);
    // Swap the buffers to display the rendered image
//  [[self openGLContext] flushBuffer];
    glFlush();
}

@end

int x = 0;
MyOpenGLView *glView;

CVReturn MyDisplayCallback(CVDisplayLinkRef displayLink,
    const CVTimeStamp *inTime,
    const CVTimeStamp *outTime,
    CVOptionFlags flagsIn,
    CVOptionFlags *flagsOut,
    void *displayLinkContext) {
        [glView prepareOpenGL];
        
        for (int i = 0; i < 20; i++ ){
            printf("-------\n");
            CVTimeStamp out;
            CVDisplayLinkGetCurrentTime(displayLink, &out);
            printf("Bef: %llu\n", out.videoTime);
            [glView drawRect:NSZeroRect];
            CVDisplayLinkGetCurrentTime(displayLink, &out);
            printf("Aft: %llu\n", out.videoTime);
        }
        CVDisplayLinkStop(displayLink);
        return kCVReturnSuccess;
}


int main(int argc, const char * argv[]) {
    // Set up the application and window
    @autoreleasepool {
        NSApplication *app = [NSApplication sharedApplication];
        [app setActivationPolicy:NSApplicationActivationPolicyRegular];

        FOTWindow *window = [[FOTWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 400)
            styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
            backing:NSBackingStoreBuffered
            defer:NO];
//      [window setBackingType:NSBackingStoreBuffered]; // New backing store is type 3, unbuffered
        [window setShowsResizeIndicator:YES];
        
        // Create an NSOpenGLPixelFormat object with double buffering enabled
        NSOpenGLPixelFormatAttribute attribs[] = {
//          NSOpenGLPFATripleBuffer,
            NSOpenGLPFAAccelerated,
            0
        };
        NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
        
        // Create an NSOpenGLContext object with the pixel format
        NSOpenGLContext *glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
        
        // Set the pixel format and context for the view
        glView = [[MyOpenGLView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400) pixelFormat:pixelFormat];
        [glView setOpenGLContext:glContext];
    
//      window.contentView = glView;
        [window setContent: glView];
        window.movableByWindowBackground = YES;
//      [window setContentView: glView];
        
        // Optionally see what happens when using a CAOpenGLLayer instance
    
      [glView setWantsLayer: NO]; // wantsLayer is actually forced
//      [glView.layer setBackgroundColor:[NSColor blueColor].CGColor];
        
        
        // Set up the window and run the application
        
//      NSView* frameView = [[window contentView] superview];
//      [glView setFrame:[frameView bounds]];
//      [frameView addSubview:glView];



//      window.contentView = glView;
        [window makeFirstResponder:glView];
        [window makeKeyAndOrderFront:nil];
    
        
        GLint a = 1;
        [glContext setValues:&a forParameter:NSOpenGLCPSwapInterval];
        
        CVDisplayLinkRef _link;
        CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(),&_link);
        
        
        CVDisplayLinkSetOutputCallback(_link, MyDisplayCallback, NULL);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            printf("Starting\n");
            CVDisplayLinkStart(_link);

        });
        
        [app run];
        
    }
    return 0;
}

Now the downside of an nsopengllayer is of course that all rendering must be done in the drawRect method, unlike an openglview where you can take the context and draw to it willy nilly outside drawRect. If you must use a non layer-backed view, there still is a way. There is an obscure NSOpenGLCPSurfaceOrder property which apple says if set to -1 "If the value is –1, the order is below the window'. This doesn't make much sense unless you consider what I mentioned before about opengl views being their own surface, like their own "window". So really this is equivalent to putting one window below another. (Likely this is an artifact from before layer backed views existed so this was a clever hack to effectively give you 2 layers reusing compositor mechanism) Now to practically make use of this, the window on top (the window that you created), has to be transparent and allow content from below to poke through. So that's what you do: you must set opacity to NO, set a transparent background color, and draw a clear rect in your associated nsopenglview, to allow content from below to poke through. Combining all that with the NSWindowFrame trick from above you can achieve something similar of having close icons directly on top of your content, with rounded corners.

The downside of all this is that as you noted a non-opaque nswindow is really slow when resizing, which other people have noted (https://lists.apple.com/archives/cocoa-dev/2012/Nov/msg00004.html or https://lists.apple.com/archives/cocoa-dev/2012/Feb/msg00667.html). There's also another option, you could take the traditional nsopenglview (opaque window, on top surface) and create another transparent window on top of it that will house your controls. This transparent window is much smaller, only needing to be the titlebar size, so it should not suffer from the same performance penalty. You can see an example of this approach here: https://github.com/insidegui/GRGlassWindow

Now going back to the rounded corners: If you actually run the above code for the transparent openglview you'll find that the bottom corners are rounded but top corners are not. This can be fixed by not drawing a full clear rect in the nsopenglview, but just drawing the rounded rect mask, which I'll leave as an exercise.

So to summarize, with a layer-backed view (in conjunction with the NSThemeFrame trick), you get top and bottom corners for free, with both OpenGL and regular quartz drawing.

With a non-layer backed view, bottom corners come "for free", and the top corners are in fact rounded as well but you need to take not to draw over them (unlike bottom corners, they don't seem to automatically clip). If you're using quartz drawing you need to mask out the corners when drawing. With an opaque nswindow and an "on top" gl surface, you have to create an opengl surface with transparency so that you don't paint over the corners. And with an "on bottom" gl surface you have to make the entire window transparent (non-opaque), and create the appropriate mask to let the content from below peek through.

But to me the really interesting thing is that the bottom corners are rounded even if you draw a non-layered opaque full-rect, meaning that somehow the rounded bottom corners are being automatically clipped/masked as needed, and this is not being done for the top corners. I tried really hard to trace how this happens, it seems as part of NSThemeFrame initialization it makes the following calls:

-[NSThemeFrame _maskCorners:clipRect:]
-[NSThemeFrame drawOverlayRect:] + 569
-[NSView _drawOverlayRectSet:] + 298
-[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] + 3138
-[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 841
-[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:] + 314
-[NSView _displayRectIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:] + 2828
-[NSView displayIfNeeded] + 1680

where the signature is - (void)_maskCorners:(NSUInteger)aFlags clipRect:(NSRect)aRect and aFlags is a bitmask whose lower-two bits determine whether you want the bottom/top corners to be rounded. (rect here doesn't seem to matter, I've even replaced it with zero without changing the effect). You can even swizzle (or sublcass NSThemeFrame and override) this method yourself and see that if you set _maskCorners to 0 then the rounded bottom corners goes away. Normally this is only called with a value of 1 (bottom corner), but disassembly shows that it shold handled a value of 0b10 for top corner as well. So can we just swizzle this to always pass 0b11 and get rounded top and bottom corners?

I was really hopeful that it would work, but it seems we're out of luck and this doesn't work quite so easily. I even tried really hard to figure out exactly what's going on under the hood: internally _maskCorners calls into CoreUI framework CUIRenderer::Draw, which ends up calling CUIArtFileRenderer::DrawWindowFrameStandard. There are 4 specific CUI image ids: 0x4e79, 0x4e7b, 0x4e56, 0x4e58 which corespond to the image id masks for the 4 rounded corners. I was even able to extract the CGImageRef equivalent and indeed they are rounded corner masks (4e56 is for the left bottom corner, 4e58 is for right bottom corner). It takes these images and calls CGContextSetCompositeOperation then CGContextDrawImages. The code for all 4 corners is essentially identical though, so it's surprising that for some reason this simply doesn't seem to work for the top corners. Maybe the calculation for position is wrong and it's never noticed because it's never exercised, or maybe the window server itself is in on this conspiracy and the rounded bottom corners only work with its assistance (perhaps related to the "Apple headers which allow window shaping" you mentioned. If you recall what these were, I'd like to know.)

One thing to try if anyone is curious is to replicate what DrawWindowFrameStandard is doing by manually invoking CGContextDrawImage and seeing if you're able to mask out portions of the window. No idea how this actually interacts with opengl content though, although it seems like it must.

咽泪装欢 2024-12-27 06:23:36

由于罗布的建议不起作用,而且没有其他人参与讨论,我决定使用模板缓冲区来裁剪窗口角。我通过从 Windows 背景创建纹理并将其渲染到模板缓冲区中,丢弃所有透明像素来做到这一点。看起来不错,但调整窗口大小时速度很慢:/

Since Robs suggestion didn't work and no one else contributed to the discussion i settled on using the stencil buffer to crop the windows corners. I did this by creating a texture from the windows background and rendering it into the stencil buffer, discarding all transparent pixels. Looks fine, but is slow when resizing the window :/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文