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

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..174bfd236f37135ad6c06c1742626283f210ca8e 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"
@@ -46,6 +48,17 @@
namespace {
+enum RepostState {
tapted 2015/06/03 07:04:49 Move this to within the RepostEventIfHandledByWind
jackhou1 2015/06/03 08:13:52 Done.
+ // Nothing reposted: hit-test new mouse-downs to see if they need to be
+ // ignored and reposted after changing draggability.
+ NONE,
+ // Expecting the next event to be the reposted event: let it go through.
+ EXPECTING_REPOST,
+ // If, while reposting, another mousedown was received: when the reposted
+ // event is seen, ignore it.
+ REPOST_CANCELLED,
+};
+
int kWindowPropertiesKey;
float GetDeviceScaleFactorFromView(NSView* view) {
@@ -77,6 +90,99 @@ 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 RepostState repost_state = NONE;
+ // The event number of the reposted event. This let's us track whether an
+ // event is actually the repost since user-generated events have increasing
+ // event numbers. This is only valid while |repost_state != NONE|.
+ static NSInteger reposted_event_number;
+
+ 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. if it is
+ // draggable outside of a repost cycle),
+ // - any repost loop.
+
+ if (repost_state == NONE) {
+ // We're not in the middle of reposting, process this new event.
tapted 2015/06/03 07:04:49 nit: this comment kinda repeats the enum comment n
jackhou1 2015/06/03 08:13:52 Done.
+ if (WindowWantsMouseDownReposted(ns_event)) {
+ repost_state = EXPECTING_REPOST;
+ reposted_event_number = [ns_event eventNumber];
tapted 2015/06/03 07:04:49 = event_number?
jackhou1 2015/06/03 08:13:51 Done.
+ CGEventPost(kCGSessionEventTap, cg_event);
+ return nil;
+ }
+
+ return ns_event;
+ }
+
+ if (repost_state == EXPECTING_REPOST) {
+ // Call through so that the window is made non-draggable again.
+ WindowWantsMouseDownReposted(ns_event);
+
+ if (reposted_event_number == event_number) {
+ // Reposted event received.
+ repost_state = NONE;
+ return nil;
+ }
+
+ // We were expecting a repost, but since this is a new mouse-down, cancel
+ // reposting and allow event to continue as usual.
+ repost_state = REPOST_CANCELLED;
+ return ns_event;
+ }
+
+ if (repost_state == REPOST_CANCELLED) {
+ if (reposted_event_number == event_number) {
+ // Reposting was cancelled, now that we've received the event, we don't
+ // expect to see it again.
+ repost_state = NONE;
+ return nil;
+ }
+
+ return ns_event;
+ }
+
+ NOTREACHED();
tapted 2015/06/03 07:04:49 instead of this, maybe DCHECK_EQ(REPOST_CANCELLED
jackhou1 2015/06/03 08:13:52 Done.
+ 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 +206,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 +639,44 @@ 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;
+
+ // Check that the point is not obscured by non-content NSViews.
+ for (NSView* subview : [[bridged_view_ superview] subviews]) {
tapted 2015/06/03 07:04:49 does something like this work: bool should_move_w
jackhou1 2015/06/03 08:13:52 The window controls are sometimes below the bridge
tapted 2015/06/03 09:12:57 Acknowledged.
+ if (subview == bridged_view_.get())
+ continue;
+
+ if (![subview mouseDownCanMoveWindow] &&
+ NSPointInRect(location_in_window, [subview frame])) {
+ should_move_window = false;
+ break;
+ }
+ }
+
+ 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 +1015,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];
+ [window_ setMovableByWindowBackground:YES];
+}
+
} // namespace views

Powered by Google App Engine
This is Rietveld 408576698