SimpleCapのプリファレンスは NSTabViewを使っていて、ツールバーによって表示内容を切り替えている。
ただウィンドウのサイズを固定にしているので、表示内容が少ない時は間抜けな状態になる。
Mail.app などでは表示内容の大きさに合わせて、ウィンドウの大きさ(高さ)を調節している。
これを SimpleCapのプリファレンスでも実現したい。まずはサンプルを作って試してみよう。
まずは動いたところから。
2つのタブがあり、初期状態はボタン「A」が表示される。ここで2番目のタブを選択してみる。
するとボタン「B」が表示され、ウィンドウはそれにあった大きさに縮む。
考え方は次の通り、「A」表示から「B」表示へ移る直前は次のような状態になっている。
座標系は左下が原点なのでウィンドウのY座標は Wy となっている。「B」ボタンのビュー内の位置を (Vx, Vy)とする。ビュー内も原点は左下となる。一方、ウィンドウの大きさが伸縮する場合左上が固定点になるので、このY座標を origin_y とする。これはウィンドウのY座標 Wy と高さから求めることができる。高さはInterfaceBuilderで指定した初期値(すなわち最大の高さ)をとっておきこれ (_window_size)を使う。
さてこれをどう縮ませるか。縮んだ時の状態が次の図。
ウィンドウの高さは、ボタン「B」のY座標の Vy 分だけ縮む。これにマージン(MARGIN_BOTTOM)を考慮すると縮む高さ diff_y が求められる。これを初期高さ _window_size.height から引けば、縮んだ後の最終的な高さがも止められる。
一方、ウィンドウは左下が原点の座標系を使っているので、左上固定で縮む場合、ウィンドウのY座標も変更する必要がある。新しいウィンドウのY座標 Wy' は origin_y を求めておけば、ここから縮んだ後の高さ(_window_size.height - diff_y)を引けば求められる。
これで縮むウィンドウができた。
なお NSWindowのサイズ指定にはいくつかメソッドがあるが、setFrame:display:animate: を使うとサイズの変更がアニメーションで表示される。
最後にソースコード解説。
ElasticWindow.h
@interface AppController : NSObject {
IBOutlet NSTabView* _tab_view;
IBOutlet NSWindow* _window;
NSSize _window_size;
}
@end
IBのアウトレットでタブとウィンドウをつかんでいる。_window_sizeは初期のウィンドウサイズ。
ElasticWindow.m
@implementation AppController
#define MARGIN_BOTTOM 10
- (void)update
{
NSTabViewItem *item = [_tab_view selectedTabViewItem];
NSArray* subviews = [[item view] subviews];
NSRect view_frame = NSZeroRect;
for (NSView* view in subviews) {
view_frame = NSUnionRect(view_frame, [view frame]);
}
NSRect window_frame = [_window frame];
CGFloat diff_y = view_frame.origin.y - MARGIN_BOTTOM;
CGFloat origin_y = window_frame.origin.y + window_frame.size.height;
window_frame.size.height = _window_size.height - diff_y;
window_frame.origin.y = origin_y - window_frame.size.height;
[_window setFrame:window_frame display:YES animate:YES];
}
- (void)awakeFromNib
{
_window_size = [_window frame].size;
[self update];
}
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[self update];
}
@end
updateメソッドが今回のキモ。上記で解説したアルゴリズムが実装されている。ビュー内のコントロール位置はサブビューを subviewsで取り出して NSUnionRectで最大領域を求め使っている。
サンプル:ElasticWindow-1.zip
今回は長かった。。