| Index: chrome/browser/ui/cocoa/bubble_anchor_helper_views.mm
|
| diff --git a/chrome/browser/ui/cocoa/bubble_anchor_helper_views.mm b/chrome/browser/ui/cocoa/bubble_anchor_helper_views.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fae302ed9a08ee6ed24f19e951e183453b4b5c3e
|
| --- /dev/null
|
| +++ b/chrome/browser/ui/cocoa/bubble_anchor_helper_views.mm
|
| @@ -0,0 +1,109 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/ui/cocoa/bubble_anchor_helper_views.h"
|
| +
|
| +#import <Cocoa/Cocoa.h>
|
| +
|
| +#import "base/mac/scoped_nsobject.h"
|
| +#include "ui/views/bubble/bubble_dialog_delegate.h"
|
| +#include "ui/views/widget/widget_observer.h"
|
| +
|
| +namespace {
|
| +
|
| +// Self-deleting object that hosts Objective-C observers watching for parent
|
| +// window resizes to reposition a bubble Widget. Deletes itself when the bubble
|
| +// Widget closes.
|
| +class BubbleAnchorHelper : public views::WidgetObserver {
|
| + public:
|
| + explicit BubbleAnchorHelper(views::BubbleDialogDelegateView* bubble);
|
| +
|
| + private:
|
| + // Observe |name| on the bubble parent window with a block to call ReAnchor().
|
| + void Observe(NSString* name);
|
| +
|
| + // Whether offset from the left of the parent window is fixed.
|
| + bool IsMinXFixed() const {
|
| + return views::BubbleBorder::is_arrow_on_left(bubble_->arrow());
|
| + }
|
| +
|
| + // Re-positions |bubble_| so that the offset to the parent window at
|
| + // construction time is preserved.
|
| + void ReAnchor();
|
| +
|
| + // WidgetObserver:
|
| + void OnWidgetDestroying(views::Widget* widget) override;
|
| +
|
| + base::scoped_nsobject<NSMutableArray> observer_tokens_;
|
| + views::BubbleDialogDelegateView* bubble_;
|
| + CGFloat horizontal_offset_; // Offset from the left or right.
|
| + CGFloat vertical_offset_; // Offset from the top.
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(BubbleAnchorHelper);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +void KeepBubbleAnchored(views::BubbleDialogDelegateView* bubble) {
|
| + new BubbleAnchorHelper(bubble);
|
| +}
|
| +
|
| +BubbleAnchorHelper::BubbleAnchorHelper(views::BubbleDialogDelegateView* bubble)
|
| + : observer_tokens_([[NSMutableArray alloc] init]), bubble_(bubble) {
|
| + DCHECK(bubble->GetWidget());
|
| + DCHECK(bubble->parent_window());
|
| + bubble->GetWidget()->AddObserver(this);
|
| +
|
| + NSRect parent_frame = [[bubble->parent_window() window] frame];
|
| + NSRect bubble_frame = [bubble->GetWidget()->GetNativeWindow() frame];
|
| +
|
| + // Note: when anchored on the right, this doesn't support changes to the
|
| + // bubble size, just the parent size.
|
| + horizontal_offset_ =
|
| + (IsMinXFixed() ? NSMinX(parent_frame) : NSMaxX(parent_frame)) -
|
| + NSMinX(bubble_frame);
|
| + vertical_offset_ = NSMaxY(parent_frame) - NSMinY(bubble_frame);
|
| +
|
| + Observe(NSWindowDidEnterFullScreenNotification);
|
| + Observe(NSWindowDidExitFullScreenNotification);
|
| + Observe(NSWindowDidResizeNotification);
|
| +
|
| + // Also monitor move. Note that for user-initiated window moves this is not
|
| + // necessary: the bubble's child window status keeps the position pinned to
|
| + // the parent during the move (which is handy since AppKit doesn't send out
|
| + // notifications until the move completes). Programmatic -[NSWindow
|
| + // setFrame:..] calls, however, do not update child window positions for us.
|
| + Observe(NSWindowDidMoveNotification);
|
| +}
|
| +
|
| +void BubbleAnchorHelper::Observe(NSString* name) {
|
| + NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| + id token = [center addObserverForName:name
|
| + object:[bubble_->parent_window() window]
|
| + queue:nil
|
| + usingBlock:^(NSNotification* notification) {
|
| + ReAnchor();
|
| + }];
|
| + [observer_tokens_ addObject:token];
|
| +}
|
| +
|
| +void BubbleAnchorHelper::ReAnchor() {
|
| + NSRect bubble_frame = [bubble_->GetWidget()->GetNativeWindow() frame];
|
| + NSRect parent_frame = [[bubble_->parent_window() window] frame];
|
| + if (IsMinXFixed())
|
| + bubble_frame.origin.x = NSMinX(parent_frame) - horizontal_offset_;
|
| + else
|
| + bubble_frame.origin.x = NSMaxX(parent_frame) - horizontal_offset_;
|
| + bubble_frame.origin.y = NSMaxY(parent_frame) - vertical_offset_;
|
| + [bubble_->GetWidget()->GetNativeWindow() setFrame:bubble_frame
|
| + display:YES
|
| + animate:NO];
|
| +}
|
| +
|
| +void BubbleAnchorHelper::OnWidgetDestroying(views::Widget* widget) {
|
| + widget->RemoveObserver(this);
|
| + for (id token in observer_tokens_.get())
|
| + [[NSNotificationCenter defaultCenter] removeObserver:token];
|
| + delete this;
|
| +}
|
|
|