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

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: Address comments. 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..0e847cf5d8fcd82dbee6995758270778b1e6972e 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,100 @@ 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
+// twice. It doesn't seem to work the first time it's reposted.
+NSEvent* DoubleRepostEventIfHandledByWindow(NSEvent* ns_event) {
+ // Which repost we're expecting to receive.
+ static int expected_repost_count = 0;
+ // 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;
+
+ 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 mousedown occurs between reposts.
+ // Specifically, we want to avoid:
+ // - BridgedNativeWidget's draggability getting out of sync (e.g. it's
+ // draggable outside of a repost cycle),
+ // - any repost loop.
+ if (reposted_event_number == event_number) {
+ // This is a reposted event.
+ if (expected_repost_count == 0) {
+ // Reposting was cancelled, now that we've received it, we don't expect
+ // to see it again.
+ reposted_event_number = -1;
+ } else if (expected_repost_count == 1) {
+ // There has been no intermediate mouse-down, repost the event again.
+ expected_repost_count = 2;
+ CGEventPost(kCGSessionEventTap, cg_event);
+ } else if (expected_repost_count == 2) {
+ expected_repost_count = 0;
+ reposted_event_number = -1;
+ // This is the final repost, call through to make the window
+ // non-draggable.
+ WindowWantsMouseDownReposted(ns_event);
+ } else {
+ NOTREACHED();
+ }
+ return nil; // Ignore the event.
+ }
+
+ // This is a new event.
+ if (expected_repost_count > 0) {
+ // We were expecting a repost, but since this is a new mouse-down, cancel
+ // reposting and allow event to continue as usual.
+ expected_repost_count = 0;
+ // 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)) {
+ expected_repost_count = 1;
+ 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) {
tapted 2015/05/25 05:24:05 I guess this looks alright :p. I don't think clang
jackhou1 2015/05/25 06:26:39 Done.
+ return DoubleRepostEventIfHandledByWindow(
+ ns_event);
+ }];
+}
+
} // namespace
namespace views {
@@ -100,6 +196,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 +629,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 +993,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.
+ base::scoped_nsobject<NSView> temp_view(
+ [[NSView alloc] initWithFrame:[bridged_view_ bounds]]);
+ [bridged_view_ addSubview:temp_view];
+ [temp_view removeFromSuperview];
+}
+
} // namespace views

Powered by Google App Engine
This is Rietveld 408576698