LoginSignup
3

More than 5 years have passed since last update.

NSTextField とか重ねて表示できる OpenGL の View を実装するには

Last updated at Posted at 2014-10-15

iwantthis.png

 上図のサンプルのように OpenGL で描画した View の上にテキストボックスを表示させたい場合、CAOpenGLLayer を使うとうまくいく。

 OpenGL の glClearColor で背景を半透明に設定したいときの注意点についてもメモしてある。

背景

  • InterfaceBuilder の Object Library にある NSOpenGLView は、子要素 (NSButtonなど) を持たせられないようになっている。
  • NSButton などを子要素とせずに NSOpenGLView の上に重ねるように設置したとしても、OpenGL の描画で NSButton などの描画が潰れてしまう。
  • NSOpenGLView の代わりに、NSOpenGLView オーバーライドした NSView を使って子要素を持たせられるようにしても、結局 NSButton などの描画が潰れてしまう。
  • 具体的には下図のような描画になってしまう。
    • テキストボックスが裏に隠れているように見えるが、実際にはテキストボックスは OpenGL サーフェスの手前に出ており、描画はされていないものの文字は入力できる状態。 bad.png

解決方法

方針

  • NSView をオーバーライドしたカスタム View クラスを作り、この View の BackingLayer で OpenGL の描画処理をやる。
  • BackingLayer には、CAOpenGLLayer を拡張したカスタム Layer クラスを使う。

サンプル実装


/* 指定した opacity の背景の上に、glut の teapot を表示するコードを何処かで実装する */
void DrawTeapotWithBackgroundOpacity(GLfloat opacity);


@interface LayerBackedGLView : NSView
{
}
@end


@interface CustomOpenGLLayer : CAOpenGLLayer
{
}
@end


@implementation CustomOpenGLLayer

-(BOOL)canDrawInCGLContext:(CGLContextObj)ctx
               pixelFormat:(CGLPixelFormatObj)pf
              forLayerTime:(CFTimeInterval)t
               displayTime:(const CVTimeStamp *)ts
{
    return YES;
}

-(void)drawInCGLContext:(CGLContextObj)ctx
            pixelFormat:(CGLPixelFormatObj)pf
           forLayerTime:(CFTimeInterval)t
            displayTime:(const CVTimeStamp *)ts
{
    CGLSetCurrentContext(ctx);
    DrawTeapotWithBackgroundOpacity(0.5);
    [super drawInCGLContext:ctx
                pixelFormat:pf
               forLayerTime:t
                displayTime:ts];
}

-(BOOL)isOpaque
{
    /* glClearColor で半透明な背景色を指定している場合、YES を返すとうまくいかない。 */
    return NO;
}

@end


@implementation LayerBackedGLView : NSView

-(id)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];

    /* ここを YES にすると、layer が必要になったタイミングで makeBackingLayer が呼ばれる */
    [self setWantsLayer:YES];

    /* Retina 対応. 以下を参照
       https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSViewOpenGLAdditions
    */
    [self setWantsBestResolutionOpenGLSurface:YES];

    return self;
}

-(CALayer*)makeBackingLayer
{
    CustomOpenGLLayer* layer = [[CustomOpenGLLayer alloc] init];
    [layer setNeedsDisplayOnBoundsChange:YES];
    return layer;
}

-(BOOL)isOpaque
{
    /* glClearColor で半透明な背景色を指定している場合、YES を返すとうまくいかない。 */
    return NO;
}

@end

注意点

OpenGL の描画で、背景色を半透明にしたい時

  • サンプル実装のコードコメントにあるように、カスタマイズした NSViewCAOpenGLLayerisOpaque 関数をオーバーライドしている場合、どちらも YES を返してはいけない。
  • isOpaque をオーバーライドしていない場合は特に何もしなくて OK。

Mac OSX Yosemite

  • CAOpenGLLayer を継承した NSOpenGLLayer というクラスがあるが、こちらを使うと Yosemite で期待したとおり動かなかった。

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3