Cocoa環境で目に優しいぼかしの入った線を描くために

メニューバーの一番上の水平ライン1pxをアプリケーション切り替えのスイッチとしていたが、現状の水平ラインの見た目はあまりにも目立たない。(メニューバーの上方向の背景は黒なので、実際にMacBookで見た時は、以下の画像よりさらに目立たない。目を凝らさないと意識できないくらいの感覚だ。)

#import "ClearWindow.h"
#import "EventView.h"


@implementation EventView

static float VIEW_HEIGHT = 22;
static float LINE_WIDTH  = 1;

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)awakeFromNib {
    //自分の高さに合わせてウィンドウ上限に再配置する
    NSRect frame = [[self window] frame];
    [self setFrame:NSMakeRect(frame.origin.x, 
                              frame.size.height - VIEW_HEIGHT, 
                              frame.size.width, 
                              VIEW_HEIGHT)];
}

- (void)drawRect:(NSRect)rect {
    //グラフィック関数で一番上に水平ラインを描画
    [[NSColor keyboardFocusIndicatorColor] set];
    NSRectFill(NSMakeRect([self bounds].origin.x, 
                          [self bounds].size.height - LINE_WIDTH, 
                          [self bounds].size.width, 
                          LINE_WIDTH));
}

@end

目立たないうえに、メニューバーを操作する時は自然と勢い余ってマウスが画面の一番上まで動きがちなので、文字メニューを操作する時に間違ってアプリケーションを切り替えてしまうという煩わしさが増えてしまった...。

ライン幅を太く

それではもう少し目たつようにということで、水平ラインを幅3pxにしてみた。

  • ライン幅3px

static float LINE_WIDTH  = 3;

- (void)drawRect:(NSRect)rect {
    //グラフィック関数で一番上に水平ラインを描画
    [[NSColor keyboardFocusIndicatorColor] set];
    NSRectFill(NSMakeRect([self bounds].origin.x, 
                          [self bounds].size.height - LINE_WIDTH, 
                          [self bounds].size.width, 
                          LINE_WIDTH));
}

確かに目立つようになったが、デザイン的にちょっと目障りな気がする...。

透明度を上げる

今度は、透明度を0.6に上げてみる。(windowにも透明度0.8を設定してあるので、目に見える時は0.8×0.6=0.48の透明度になっていると思う。)

  • ライン幅3px
  • 透明度0.6

static float LINE_WIDTH  = 3;

- (void)drawRect:(NSRect)rect {
    //グラフィック関数で一番上に水平ラインを描画
    [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.6] set];
    NSRectFill(NSMakeRect([self bounds].origin.x, 
                          [self bounds].size.height - LINE_WIDTH, 
                          [self bounds].size.width, 
                          LINE_WIDTH));
}

存在感は薄くなったが、目障りな感じは残っている。MacOSX環境のGUIデザインと調和させるためには、もうひと工夫必要な気がする。

影を付ける

ライン幅を少し細くして、さらに同じ水色の影を付けてみた。

  • ライン幅2px
  • 透明度1.0
  • 影付き

static float LINE_WIDTH  = 2;

- (void)drawRect:(NSRect)rect {
    //影を付けてぼかす
    NSShadow *shadow = [[[NSShadow alloc] init] autorelease];
    [shadow setShadowColor:[NSColor keyboardFocusIndicatorColor]];
    [shadow setShadowOffset:NSMakeSize(0^K, ^K-1)];
    [shadow setShadowBlurRadius:2];
    [shadow set];

    //グラフィック関数で一番上に水平ラインを描画
    [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:1.0] set];
    NSRectFill(NSMakeRect([self bounds].origin.x, 
                          [self bounds].size.height - LINE_WIDTH, 
                          [self bounds].size.width, 
                          LINE_WIDTH));
}

いい感じになった。ラインの境界がぼやけることで、これなら目に優しい気がする。

ところで、一番上にぼかしの入った水平ラインを描くには上記方法で十分なのだが、それでは一番下に描きたい時は?または、ぼかしの入った縦ラインを描きたい時は?という疑問が浮かんだ...。NSShadowは、光源の位置を自由に設定できるのだろうか?自分で調べた限りその方法は分からなかった。境界をぼかすという処理は、ある規則に従って透明度のグラデーションを設定してあげれば良さそうな気がするが、それを自分で書くのはちょっと大変そうだ。(この場合は、直線なので簡単かもしれないが)

フォーカスリングでぼかす

MacOSXには、TabキーでテキストフィールドなどのGUI部品間を移動した場合、今現在キー入力の操作対象になっていることを明示するフォーカスリングが表示される。このフォーカスリングを自分で設計したNSViewクラスに自由に設定できれば、問題は解決する。その結果、影を付けるために必要だった5行のコードは、フォーカスリングならたった1行で済んでしまった。ぼけ具合は影付きの時と似たような感じだ。

  • ライン幅1px
  • 透明度1.0
  • 影なし
  • フォーカスリングあり

static float LINE_WIDTH  = 1;

- (void)drawRect:(NSRect)rect {
    //グラフィック関数で一番上に水平ラインを描画
    [[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:1.0] set];
    NSSetFocusRingStyle(NSFocusRingOnly);//このあと描画した図形の周囲にフォーカスリングが表示される
    NSRectFill(NSMakeRect([self bounds].origin.x, 
                          [self bounds].size.height - LINE_WIDTH, 
                          [self bounds].size.width, 
                          LINE_WIDTH));
}
自由形

フォーカスリングは自由な形状に設定出来るらしい。使い方として不思議な感じがするが、NSSetFocusRingStyle(NSFocusRingOnly);と書いたそのあと、描画した図形の周囲にフォーカスリングが表示されるようだ。

  • NSFrameRect(rect)とすれば、指定したrect四角形を描くラインの両側に描画される。
  • NSRectFill(rect)とすれば、rect四角形の周囲に描画される。
  • NSBezierPathで自由な形状にすることも可能。
参考ページ

静かな午後 - tsawada2さんのMacページがたいへん参考になりました。感謝です!(自分でも全く同じ経過を辿り試行錯誤していました...。)