Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1969)

Unified Diff: ui/views/cocoa/bridged_native_widget.mm

Issue 1747803003: MacViews: Implement Tab Dragging (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Extract DragsWindowUsingCocoaMoveLoop test, cleanup code. Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/views/cocoa/bridged_native_widget.mm
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index 6f5dec0f96864ede56c8a7cf940de5ebf964aa92..3671b1eb63511e47b7aeda036ad7a56dcf13cc5a 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -12,6 +12,7 @@
#import "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#import "base/mac/sdk_forward_declarations.h"
+#include "base/run_loop.h"
#include "base/thread_task_runner_handle.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
@@ -35,16 +36,6 @@
#include "ui/views/widget/widget_aura_utils.h"
#include "ui/views/widget/widget_delegate.h"
-extern "C" {
-
-typedef int32_t CGSConnection;
-CGSConnection _CGSDefaultConnection();
-CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection,
- NSInteger windowNumber,
- int radius);
-
-}
-
// The NSView that hosts the composited CALayer drawing the UI. It fills the
// window but is not hittable so that accessibility hit tests always go to the
// BridgedContentView.
@@ -94,6 +85,8 @@ const int kResizeAreaCornerSize = 12;
int kWindowPropertiesKey;
+bool g_ignore_next_mouse_down_for_draggable_regions = false;
+
float GetDeviceScaleFactorFromView(NSView* view) {
gfx::Display display =
gfx::Screen::GetScreen()->GetDisplayNearestWindow(view);
@@ -191,6 +184,11 @@ NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
// - any repost loop.
if (repost_state == NONE) {
+ if (g_ignore_next_mouse_down_for_draggable_regions) {
+ g_ignore_next_mouse_down_for_draggable_regions = false;
+ return ns_event;
+ }
+
if (WindowWantsMouseDownReposted(ns_event)) {
repost_state = EXPECTING_REPOST;
reposted_event_number = event_number;
@@ -263,10 +261,215 @@ scoped_refptr<base::SingleThreadTaskRunner> GetCompositorTaskRunner() {
return task_runner ? task_runner : base::ThreadTaskRunnerHandle::Get();
}
+CGPoint GetCGMousePosition() {
+ return CGEventGetLocation(
+ base::ScopedCFTypeRef<CGEventRef>(CGEventCreate(nullptr)));
+}
+
+void SendCustomLeftMouseEvent(CGEventType type) {
+ base::ScopedCFTypeRef<CGEventRef> event(
+ CGEventCreateMouseEvent(
+ nullptr, type, GetCGMousePosition(),
+ kCGMouseButtonLeft));
+ CGEventSetIntegerValueField(event, kCGEventSourceUserData, 1);
+ CGEventPost(kCGSessionEventTap, event);
+}
+
} // namespace
+extern "C" {
+
+typedef int32_t CGSConnection;
+CGSConnection _CGSDefaultConnection();
+CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection,
+ NSInteger windowNumber,
+ int radius);
+
+}
+
+// CocoaWindowMoveLoop can be deleted just before its local event monitor is
+// processed and in that case it's too late to call removeMonitor: and we have a
+// dangling this pointer. Use a proxy Obj-C class to store the weakptr.
+@interface WeakCocoaWindowMoveLoop : NSObject {
+ @private
+ base::WeakPtr<views::CocoaWindowMoveLoop> weak_;
+}
+@end
+
+@implementation WeakCocoaWindowMoveLoop
+- (id)initWithWeakPtr:(const base::WeakPtr<views::CocoaWindowMoveLoop>&)weak {
+ if (self = [super init]) {
+ weak_ = weak;
+ }
+ return self;
+}
+
+- (base::WeakPtr<views::CocoaWindowMoveLoop>&)weak {
+ return weak_;
+}
+@end
+
namespace views {
+class CocoaWindowMoveLoop {
tapted 2016/03/10 11:51:19 Move this to its own file (with WeakCocoaMoveLoop?
themblsha 2016/03/10 17:18:58 Oh, right, you told me about that and it seems tha
+ public:
+ explicit CocoaWindowMoveLoop(BridgedNativeWidget* owner)
+ : owner_(owner),
+ run_loop_(new base::RunLoop),
+ quit_closure_(run_loop_->QuitClosure()),
+ weak_factory_(this) {
+ // AppKit continues to send mouse events to the window, but toolkit-views
+ // goes berserk if it gets them during RunMoveLoop().
tapted 2016/03/10 11:51:19 hehe - I remember writing `berserk`... Perhaps "bu
themblsha 2016/03/10 17:18:58 Done.
+ [owner_->ns_view() setIgnoreMouseEvents:YES];
+ owner_->SetDraggable(true);
+ }
+
+ ~CocoaWindowMoveLoop() {
+ owner_->SetDraggable(false);
+ // Note the crafted events in Run() should not make their way through to
+ // toolkit-views, but regular events after that should be. So stop ignoring.
+ [owner_->ns_view() setIgnoreMouseEvents:NO];
+
+ // Handle the pathological case, where |this| is destroyed while running.
+ if (exit_reason_ref_) {
+ *exit_reason_ref_ = WINDOW_DESTROYED;
+ quit_closure_.Run();
+ }
+
+ // Handle Run() never being called.
+ if (monitor_) {
+ [NSEvent removeMonitor:monitor_];
+ monitor_ = nil;
+ }
+ owner_ = nullptr;
+ }
+
+ Widget::MoveLoopResult Run() {
+ LoopExitReason exit_reason = ENDED_EXTERNALLY;
+ exit_reason_ref_ = &exit_reason;
+
+ // Move the RunLoop to the stack so our destructor can be called while it is
+ // running.
+ scoped_ptr<base::RunLoop> run_loop(std::move(run_loop_));
+
+ // A new window may have just been created, so post an event at the session
+ // tap level to initiate a window drag.
+ // TODO(tapted): Move this "inside" the window or stuff breaks when dragging
+ // the last tab/s off a browser.
+ SendCustomLeftMouseEvent(kCGEventLeftMouseDown);
+
+ WeakCocoaWindowMoveLoop* proxy_quit_closure =
+ [[[WeakCocoaWindowMoveLoop alloc]
+ initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease];
+
+ NSEventMask mask =
+ NSLeftMouseUpMask | NSKeyDownMask | NSLeftMouseDraggedMask;
+ monitor_ = [NSEvent addLocalMonitorForEventsMatchingMask:mask
+ handler:^NSEvent*(NSEvent* event) {
+ if ([event type] == NSLeftMouseDragged) {
+ // AppKit doesn't supply position updates during a drag, so post a
+ // task to notify observers once AppKit has moved the window.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&BridgedNativeWidget::OnPositionChanged,
+ base::Unretained(owner_)));
+ return event;
+ }
+
+ CocoaWindowMoveLoop* thiss = [proxy_quit_closure weak].get();
+ if (!thiss)
+ return nil;
+
+ thiss->quit_closure_.Run();
+ if ([event type] == NSLeftMouseUp) {
+ *thiss->exit_reason_ref_ = MOUSE_UP;
+ return event; // Process the MouseUp.
+ }
+ *thiss->exit_reason_ref_ = ESCAPE_PRESSED;
+ return nil; // Swallow the keypress.
+ }];
+
+ // NSKeyDownMask doesn't work inside addLocalMonitorForEventsMatchingMask:
+ // the event is swallowed by the window move loop before it gets to -[NSApp
+ // sendEvent:]. To see an escape keypress, hook in an event tap lower.
+
+ run_loop->Run();
+
+ if (exit_reason != WINDOW_DESTROYED && exit_reason != ENDED_EXTERNALLY) {
+ exit_reason_ref_ = nullptr; // Ensure End() doesn't replace the reason.
+ owner_->EndMoveLoop(); // Deletes |this|.
+ }
+
+ if (exit_reason != MOUSE_UP) {
+ // Tell AppKit to stop moving the window. Otherwise, AppKit will refuse to
+ // start a new window drag. Note the window being dragged is going away in
+ // this case, so it doesn't really matter where the event is located.
+ SendCustomLeftMouseEvent(kCGEventLeftMouseUp);
+
+ if (exit_reason == ENDED_EXTERNALLY) {
+ // When not canceled, the non-moving drag in the original window must
+ // resume. To do this, AppKit needs to see a mouseDown so that it sends
+ // the correct events. Ideally, it also needs to be at the original
+ // offset, so that it hits a non-draggable region of the original
+ // window: The tab being dragged may move some distance from the cursor
+ // when it "snaps in", so the cursor may not be over a tab. Sadly, this
+ // method doesn't know which window that is. But all that really needs
+ // to be done is to prevent a custom-dragging area from starting a
+ // window-drag. So hook into the logic in RepostEventIfHandledByWindow()
+ // by setting a flag here. Note this assumes the custom mouseDown event
+ // is guaranteed to hit another BridgedNativeWidget when it gets to the
+ // front of the event queue.
+ // TODO(tapted): A better fix would be to keep the temporary window
+ // around and never call EndMoveLoop() on Mac, making this block of code
+ // obsolete.
+ g_ignore_next_mouse_down_for_draggable_regions = true;
+ SendCustomLeftMouseEvent(kCGEventLeftMouseDown);
+ }
+ }
+
+ return exit_reason == ESCAPE_PRESSED
+ ? Widget::MOVE_LOOP_CANCELED : Widget::MOVE_LOOP_SUCCESSFUL;
+ }
+
+ void End() {
+ if (exit_reason_ref_) {
+ DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY);
+ // Ensure the destructor doesn't replace the reason.
+ exit_reason_ref_ = nullptr;
+ quit_closure_.Run();
+ }
+ }
+
+ static CGEventRef EscapeKeypressMonitor(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon) {
+ CocoaWindowMoveLoop* self = static_cast<CocoaWindowMoveLoop*>(refcon);
+ (void)self;
+ return event;
+ }
+
+ private:
+ enum LoopExitReason {
+ ENDED_EXTERNALLY,
+ ESCAPE_PRESSED,
+ MOUSE_UP,
+ WINDOW_DESTROYED,
+ };
+
+ BridgedNativeWidget* owner_; // Weak. Owns this.
+ scoped_ptr<base::RunLoop> run_loop_;
+ id monitor_ = nil;
+
+ // Pointer to a stack variable holding the exit reason.
+ LoopExitReason* exit_reason_ref_ = nullptr;
+ base::Closure quit_closure_;
+
+ // WeakPtrFactory for event monitor safety.
+ base::WeakPtrFactory<CocoaWindowMoveLoop> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CocoaWindowMoveLoop);
+};
+
// static
gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
NSWindow* window,
@@ -569,6 +772,49 @@ bool BridgedNativeWidget::HasCapture() {
return mouse_capture_ && mouse_capture_->IsActive();
}
+Widget::MoveLoopResult BridgedNativeWidget::RunMoveLoop(
+ const gfx::Vector2d& drag_offset) {
+ DCHECK(!HasCapture());
+ DCHECK(!window_move_loop_);
+
+ // First, position the window in the right place. The point |drag_offset|
+ // away from the top-left corner needs to be positioned under the mouse.
+ // TODO(tapted): Figure out why the toolkit-views drag controller doesn't get
+ // this right when it first initializes the Widget.
+ NSPoint mouse_in_screen = gfx::ScreenPointToNSPoint(
+ gfx::Screen::GetScreen()->GetCursorScreenPoint());
+ NSRect frame = [window_ frame];
+ frame.origin.x = mouse_in_screen.x - drag_offset.x();
+ frame.origin.y = mouse_in_screen.y - NSHeight(frame) + drag_offset.y();
+
+ // After setting the frame to correct the initial offset, the drag controller
+ // may immediately want to quit when it's notified of the new bounds. So the
+ // MoveLoop must be set up before the call to setFrame.
+ window_move_loop_.reset(new CocoaWindowMoveLoop(this));
+
+ // Animating may provide a less janky UX, but something custom would be
+ // required so that it follows updates to the mouse position.
+ [window_ setFrame:frame display:YES animate:NO];
+
+ // Setting the frame will call OnWidgetBoundsChanged(), which could result in
+ // a call to EndMoveLoop().
+ if (!window_move_loop_)
+ return Widget::MOVE_LOOP_SUCCESSFUL;
+
+ return window_move_loop_->Run();
+
+ // |this| may be destroyed during the RunLoop, causing it to exit early.
+ // Even if that doesn't happen, CocoaWindowMoveLoop will clean itself up by
+ // calling EndMoveLoop(). So window_move_loop_ will always be null before the
+ // function returns. But don't DCHECK since |this| might not be valid.
+}
+
+void BridgedNativeWidget::EndMoveLoop() {
+ DCHECK(window_move_loop_);
+ window_move_loop_->End();
+ window_move_loop_.reset();
+}
+
void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
void* value) {
NSString* key = [NSString stringWithUTF8String:name];
@@ -590,6 +836,7 @@ void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
}
void BridgedNativeWidget::OnWindowWillClose() {
+ DCHECK(!drag_run_loop_);
if (parent_) {
parent_->RemoveChildWindow(this);
parent_ = nullptr;
@@ -692,6 +939,10 @@ void BridgedNativeWidget::OnSizeChanged() {
[bridged_view_ updateWindowMask];
}
+void BridgedNativeWidget::OnPositionChanged() {
+ native_widget_mac_->GetWidget()->OnNativeWidgetMove();
+}
+
void BridgedNativeWidget::OnVisibilityChanged() {
OnVisibilityChangedTo([window_ isVisible]);
}

Powered by Google App Engine
This is Rietveld 408576698