| 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 3e281b595559f1fc079268f30458cbfa4e971d9a..ec3f7eb1d582d59110079617c7eb68c8048c2c11 100644
|
| --- a/ui/views/cocoa/bridged_native_widget.mm
|
| +++ b/ui/views/cocoa/bridged_native_widget.mm
|
| @@ -7,10 +7,12 @@
|
| #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"
|
| #import "ui/base/cocoa/constrained_window/constrained_window_animation.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"
|
| @@ -78,6 +80,104 @@ 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) {
|
| + enum RepostState {
|
| + // 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,
|
| + };
|
| +
|
| + // 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;
|
| +
|
| + 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) {
|
| + if (WindowWantsMouseDownReposted(ns_event)) {
|
| + repost_state = EXPECTING_REPOST;
|
| + reposted_event_number = event_number;
|
| + CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
|
| + 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;
|
| + }
|
| +
|
| + DCHECK_EQ(REPOST_CANCELLED, repost_state);
|
| + 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;
|
| +}
|
| +
|
| +// 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 {
|
| @@ -101,6 +201,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]);
|
| @@ -548,6 +649,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]) {
|
| + 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
|
| @@ -886,4 +1025,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
|
|
|