Chromium Code Reviews| 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 |