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

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

Issue 1146873002: [MacViews] Enable dragging a window by its caption/draggable areas. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Change to single repost and [NSWindow setMovableByWindowBackground]. Created 5 years, 7 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 78abdb837c97ba5c25f94525c20d25606e7990a9..67003cad41d75e90ff7c5b16956a70683057c252 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -7,9 +7,11 @@
#import <objc/runtime.h>
#include "base/logging.h"
+#import "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#import "base/mac/sdk_forward_declarations.h"
#include "base/thread_task_runner_handle.h"
+#include "ui/base/hit_test.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/input_method_factory.h"
#include "ui/base/ui_base_switches_util.h"
@@ -77,6 +79,92 @@ gfx::Size GetClientSizeForWindowSize(NSWindow* window,
return gfx::Size([window contentRectForFrameRect:frame_rect].size);
}
+BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
+ id delegate = [[ns_event window] delegate];
+ return
+ [delegate
+ respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
+ [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
+}
+
+// Check if a mouse-down event should drag the window. If so, repost the event.
+NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
+ // Which repost we're expecting to receive.
+ static bool expecting_repost = false;
+ // The event number of the reposted event. This let's us track whether an
+ // event is actually a repost since user-generated events have increasing
+ // event numbers.
+ static NSInteger reposted_event_number = -1;
tapted 2015/06/03 00:28:37 did we want to try this out with 0 instead of -1?
jackhou1 2015/06/03 04:04:44 Done.
+
+ CGEventRef cg_event = [ns_event CGEvent];
+ NSInteger event_number = [ns_event eventNumber];
+
+ // The logic here is a bit convoluted because we want to mitigate race
+ // conditions if somehow a different mouse-down occurs between reposts.
+ // Specifically, we want to avoid:
+ // - BridgedNativeWidget's draggability getting out of sync (e.g. it's
tapted 2015/06/03 00:28:37 nit: it's -> its
jackhou1 2015/06/03 04:04:44 This is actually an it's. Changed to "if it is".
tapted 2015/06/03 07:04:48 whoops! I must have read as "its draggability outs
+ // draggable outside of a repost cycle),
+ // - any repost loop.
+ if (reposted_event_number == event_number) {
+ // This is a reposted event.
+ if (expecting_repost) {
+ expecting_repost = false;
+ reposted_event_number = -1;
+ // This is the final repost, call through to make the window
+ // non-draggable.
+ WindowWantsMouseDownReposted(ns_event);
+ } else {
+ // Reposting was cancelled, now that we've received it, we don't expect
+ // to see it again.
+ reposted_event_number = -1;
+ }
+ return nil; // Ignore the event.
+ }
+
+ // This is a new event.
+ if (expecting_repost) {
+ // We were expecting a repost, but since this is a new mouse-down, cancel
+ // reposting and allow event to continue as usual.
+ expecting_repost = false;
+ // Call through so that the window is made non-draggable again.
+ WindowWantsMouseDownReposted(ns_event);
+ return ns_event;
+ }
+
+ // We're not in the middle of reposting, process this new event.
+ if (WindowWantsMouseDownReposted(ns_event)) {
+ expecting_repost = true;
+ reposted_event_number = [ns_event eventNumber];
+ CGEventPost(kCGSessionEventTap, cg_event);
+ return nil;
+ }
+
+ return ns_event;
+}
+
+// Support window caption/draggable regions.
+// In AppKit, non-client regions are set by overriding
+// -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
+// installed and performs window moving when mouse-downs land in the area.
+// In Views, non-client regions are determined via hit-tests when the event
+// occurs.
+// To bridge the two models, we monitor mouse-downs with
+// +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
+// events after window dragging is handled, so for mouse-downs that land on a
+// draggable point, we cancel the event and repost it at the CGSessionEventTap
+// level so that window dragging will be handled again.
+void SetupDragEventMonitor() {
+ static id monitor = nil;
+ if (monitor)
+ return;
+
+ monitor = [NSEvent
+ addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
+ handler:^NSEvent*(NSEvent* ns_event) {
+ return RepostEventIfHandledByWindow(ns_event);
+ }];
+}
+
} // namespace
namespace views {
@@ -100,6 +188,7 @@ BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
in_fullscreen_transition_(false),
window_visible_(false),
wants_to_be_visible_(false) {
+ SetupDragEventMonitor();
DCHECK(parent);
window_delegate_.reset(
[[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
@@ -532,6 +621,32 @@ void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
}
}
+bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
+ NSPoint location_in_window) {
+ if (!bridged_view_)
+ return false;
+
+ if ([bridged_view_ mouseDownCanMoveWindow]) {
+ // This is a re-post, the movement has already started, so we can make the
+ // window non-draggable again.
+ SetDraggable(false);
+ return false;
+ }
+
+ gfx::Point point(location_in_window.x,
+ NSHeight([window_ frame]) - location_in_window.y);
+ bool should_move_window =
+ native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
+ HTCAPTION;
+
+ if (!should_move_window)
+ return false;
+
+ // Make the window draggable, then return true to repost the event.
+ SetDraggable(true);
+ return true;
+}
+
void BridgedNativeWidget::OnSizeConstraintsChanged() {
// Don't modify the size constraints or fullscreen collection behavior while
// in fullscreen or during a transition. OnFullscreenTransitionComplete will
@@ -870,4 +985,14 @@ NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
return properties;
}
+void BridgedNativeWidget::SetDraggable(bool draggable) {
+ [bridged_view_ setMouseDownCanMoveWindow:draggable];
+ // AppKit will not update its cache of mouseDownCanMoveWindow unless something
+ // changes. Previously we tried adding an NSView and removing it, but for some
+ // reason it required reposting the mouse-down event, and didn't always work.
+ // Calling the below seems to be an effective solution.
+ [window_ setMovableByWindowBackground:NO];
jackhou1 2015/06/02 06:33:09 I'm not sure exactly why it wasn't working for bro
tapted 2015/06/03 00:28:37 Nice!
+ [window_ setMovableByWindowBackground:YES];
+}
+
} // namespace views

Powered by Google App Engine
This is Rietveld 408576698