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

Side by Side 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, 6 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 unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ui/views/cocoa/bridged_native_widget.h" 5 #import "ui/views/cocoa/bridged_native_widget.h"
6 6
7 #import <objc/runtime.h> 7 #import <objc/runtime.h>
8 8
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #import "base/mac/foundation_util.h"
10 #include "base/mac/mac_util.h" 11 #include "base/mac/mac_util.h"
11 #import "base/mac/sdk_forward_declarations.h" 12 #import "base/mac/sdk_forward_declarations.h"
12 #include "base/thread_task_runner_handle.h" 13 #include "base/thread_task_runner_handle.h"
14 #include "ui/base/hit_test.h"
13 #include "ui/base/ime/input_method.h" 15 #include "ui/base/ime/input_method.h"
14 #include "ui/base/ime/input_method_factory.h" 16 #include "ui/base/ime/input_method_factory.h"
15 #include "ui/base/ui_base_switches_util.h" 17 #include "ui/base/ui_base_switches_util.h"
16 #include "ui/gfx/display.h" 18 #include "ui/gfx/display.h"
17 #include "ui/gfx/geometry/dip_util.h" 19 #include "ui/gfx/geometry/dip_util.h"
18 #import "ui/gfx/mac/coordinate_conversion.h" 20 #import "ui/gfx/mac/coordinate_conversion.h"
19 #import "ui/gfx/mac/nswindow_frame_controls.h" 21 #import "ui/gfx/mac/nswindow_frame_controls.h"
20 #include "ui/gfx/screen.h" 22 #include "ui/gfx/screen.h"
21 #import "ui/views/cocoa/bridged_content_view.h" 23 #import "ui/views/cocoa/bridged_content_view.h"
22 #import "ui/views/cocoa/cocoa_mouse_capture.h" 24 #import "ui/views/cocoa/cocoa_mouse_capture.h"
(...skipping 16 matching lines...) Expand all
39 @end 41 @end
40 42
41 @implementation ViewsCompositorSuperview 43 @implementation ViewsCompositorSuperview
42 - (NSView*)hitTest:(NSPoint)aPoint { 44 - (NSView*)hitTest:(NSPoint)aPoint {
43 return nil; 45 return nil;
44 } 46 }
45 @end 47 @end
46 48
47 namespace { 49 namespace {
48 50
51 enum RepostState {
tapted 2015/06/03 07:04:49 Move this to within the RepostEventIfHandledByWind
jackhou1 2015/06/03 08:13:52 Done.
52 // Nothing reposted: hit-test new mouse-downs to see if they need to be
53 // ignored and reposted after changing draggability.
54 NONE,
55 // Expecting the next event to be the reposted event: let it go through.
56 EXPECTING_REPOST,
57 // If, while reposting, another mousedown was received: when the reposted
58 // event is seen, ignore it.
59 REPOST_CANCELLED,
60 };
61
49 int kWindowPropertiesKey; 62 int kWindowPropertiesKey;
50 63
51 float GetDeviceScaleFactorFromView(NSView* view) { 64 float GetDeviceScaleFactorFromView(NSView* view) {
52 gfx::Display display = 65 gfx::Display display =
53 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); 66 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
54 DCHECK(display.is_valid()); 67 DCHECK(display.is_valid());
55 return display.device_scale_factor(); 68 return display.device_scale_factor();
56 } 69 }
57 70
58 // Returns true if bounds passed to window in SetBounds should be treated as 71 // Returns true if bounds passed to window in SetBounds should be treated as
(...skipping 11 matching lines...) Expand all
70 gfx::Size GetClientSizeForWindowSize(NSWindow* window, 83 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
71 const gfx::Size& window_size) { 84 const gfx::Size& window_size) {
72 NSRect frame_rect = 85 NSRect frame_rect =
73 NSMakeRect(0, 0, window_size.width(), window_size.height()); 86 NSMakeRect(0, 0, window_size.width(), window_size.height());
74 // Note gfx::Size will prevent dimensions going negative. They are allowed to 87 // Note gfx::Size will prevent dimensions going negative. They are allowed to
75 // be zero at this point, because Widget::GetMinimumSize() may later increase 88 // be zero at this point, because Widget::GetMinimumSize() may later increase
76 // the size. 89 // the size.
77 return gfx::Size([window contentRectForFrameRect:frame_rect].size); 90 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
78 } 91 }
79 92
93 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
94 id delegate = [[ns_event window] delegate];
95 return
96 [delegate
97 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
98 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
99 }
100
101 // Check if a mouse-down event should drag the window. If so, repost the event.
102 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
103 // Which repost we're expecting to receive.
104 static RepostState repost_state = NONE;
105 // The event number of the reposted event. This let's us track whether an
106 // event is actually the repost since user-generated events have increasing
107 // event numbers. This is only valid while |repost_state != NONE|.
108 static NSInteger reposted_event_number;
109
110 CGEventRef cg_event = [ns_event CGEvent];
111 NSInteger event_number = [ns_event eventNumber];
112
113 // The logic here is a bit convoluted because we want to mitigate race
114 // conditions if somehow a different mouse-down occurs between reposts.
115 // Specifically, we want to avoid:
116 // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is
117 // draggable outside of a repost cycle),
118 // - any repost loop.
119
120 if (repost_state == NONE) {
121 // 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.
122 if (WindowWantsMouseDownReposted(ns_event)) {
123 repost_state = EXPECTING_REPOST;
124 reposted_event_number = [ns_event eventNumber];
tapted 2015/06/03 07:04:49 = event_number?
jackhou1 2015/06/03 08:13:51 Done.
125 CGEventPost(kCGSessionEventTap, cg_event);
126 return nil;
127 }
128
129 return ns_event;
130 }
131
132 if (repost_state == EXPECTING_REPOST) {
133 // Call through so that the window is made non-draggable again.
134 WindowWantsMouseDownReposted(ns_event);
135
136 if (reposted_event_number == event_number) {
137 // Reposted event received.
138 repost_state = NONE;
139 return nil;
140 }
141
142 // We were expecting a repost, but since this is a new mouse-down, cancel
143 // reposting and allow event to continue as usual.
144 repost_state = REPOST_CANCELLED;
145 return ns_event;
146 }
147
148 if (repost_state == REPOST_CANCELLED) {
149 if (reposted_event_number == event_number) {
150 // Reposting was cancelled, now that we've received the event, we don't
151 // expect to see it again.
152 repost_state = NONE;
153 return nil;
154 }
155
156 return ns_event;
157 }
158
159 NOTREACHED();
tapted 2015/06/03 07:04:49 instead of this, maybe DCHECK_EQ(REPOST_CANCELLED
jackhou1 2015/06/03 08:13:52 Done.
160 return ns_event;
161 }
162
163 // Support window caption/draggable regions.
164 // In AppKit, non-client regions are set by overriding
165 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
166 // installed and performs window moving when mouse-downs land in the area.
167 // In Views, non-client regions are determined via hit-tests when the event
168 // occurs.
169 // To bridge the two models, we monitor mouse-downs with
170 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
171 // events after window dragging is handled, so for mouse-downs that land on a
172 // draggable point, we cancel the event and repost it at the CGSessionEventTap
173 // level so that window dragging will be handled again.
174 void SetupDragEventMonitor() {
175 static id monitor = nil;
176 if (monitor)
177 return;
178
179 monitor = [NSEvent
180 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
181 handler:^NSEvent*(NSEvent* ns_event) {
182 return RepostEventIfHandledByWindow(ns_event);
183 }];
184 }
185
80 } // namespace 186 } // namespace
81 187
82 namespace views { 188 namespace views {
83 189
84 // static 190 // static
85 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( 191 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
86 NSWindow* window, 192 NSWindow* window,
87 const gfx::Size& content_size) { 193 const gfx::Size& content_size) {
88 NSRect content_rect = 194 NSRect content_rect =
89 NSMakeRect(0, 0, content_size.width(), content_size.height()); 195 NSMakeRect(0, 0, content_size.width(), content_size.height());
90 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 196 NSRect frame_rect = [window frameRectForContentRect:content_rect];
91 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 197 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
92 } 198 }
93 199
94 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) 200 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
95 : native_widget_mac_(parent), 201 : native_widget_mac_(parent),
96 focus_manager_(nullptr), 202 focus_manager_(nullptr),
97 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). 203 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
98 parent_(nullptr), 204 parent_(nullptr),
99 target_fullscreen_state_(false), 205 target_fullscreen_state_(false),
100 in_fullscreen_transition_(false), 206 in_fullscreen_transition_(false),
101 window_visible_(false), 207 window_visible_(false),
102 wants_to_be_visible_(false) { 208 wants_to_be_visible_(false) {
209 SetupDragEventMonitor();
103 DCHECK(parent); 210 DCHECK(parent);
104 window_delegate_.reset( 211 window_delegate_.reset(
105 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); 212 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
106 } 213 }
107 214
108 BridgedNativeWidget::~BridgedNativeWidget() { 215 BridgedNativeWidget::~BridgedNativeWidget() {
109 RemoveOrDestroyChildren(); 216 RemoveOrDestroyChildren();
110 DCHECK(child_windows_.empty()); 217 DCHECK(child_windows_.empty());
111 SetFocusManager(NULL); 218 SetFocusManager(NULL);
112 SetRootView(NULL); 219 SetRootView(NULL);
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after
525 if (is_key) { 632 if (is_key) {
526 widget->OnNativeFocus(); 633 widget->OnNativeFocus();
527 widget->GetFocusManager()->RestoreFocusedView(); 634 widget->GetFocusManager()->RestoreFocusedView();
528 } else { 635 } else {
529 widget->OnNativeBlur(); 636 widget->OnNativeBlur();
530 widget->GetFocusManager()->StoreFocusedView(true); 637 widget->GetFocusManager()->StoreFocusedView(true);
531 } 638 }
532 } 639 }
533 } 640 }
534 641
642 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
643 NSPoint location_in_window) {
644 if (!bridged_view_)
645 return false;
646
647 if ([bridged_view_ mouseDownCanMoveWindow]) {
648 // This is a re-post, the movement has already started, so we can make the
649 // window non-draggable again.
650 SetDraggable(false);
651 return false;
652 }
653
654 gfx::Point point(location_in_window.x,
655 NSHeight([window_ frame]) - location_in_window.y);
656 bool should_move_window =
657 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
658 HTCAPTION;
659
660 // Check that the point is not obscured by non-content NSViews.
661 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.
662 if (subview == bridged_view_.get())
663 continue;
664
665 if (![subview mouseDownCanMoveWindow] &&
666 NSPointInRect(location_in_window, [subview frame])) {
667 should_move_window = false;
668 break;
669 }
670 }
671
672 if (!should_move_window)
673 return false;
674
675 // Make the window draggable, then return true to repost the event.
676 SetDraggable(true);
677 return true;
678 }
679
535 void BridgedNativeWidget::OnSizeConstraintsChanged() { 680 void BridgedNativeWidget::OnSizeConstraintsChanged() {
536 // Don't modify the size constraints or fullscreen collection behavior while 681 // Don't modify the size constraints or fullscreen collection behavior while
537 // in fullscreen or during a transition. OnFullscreenTransitionComplete will 682 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
538 // reset these after leaving fullscreen. 683 // reset these after leaving fullscreen.
539 if (target_fullscreen_state_ || in_fullscreen_transition_) 684 if (target_fullscreen_state_ || in_fullscreen_transition_)
540 return; 685 return;
541 686
542 Widget* widget = native_widget_mac()->GetWidget(); 687 Widget* widget = native_widget_mac()->GetWidget();
543 gfx::Size min_size = widget->GetMinimumSize(); 688 gfx::Size min_size = widget->GetMinimumSize();
544 gfx::Size max_size = widget->GetMaximumSize(); 689 gfx::Size max_size = widget->GetMaximumSize();
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after
863 NSMutableDictionary* properties = objc_getAssociatedObject( 1008 NSMutableDictionary* properties = objc_getAssociatedObject(
864 window_, &kWindowPropertiesKey); 1009 window_, &kWindowPropertiesKey);
865 if (!properties) { 1010 if (!properties) {
866 properties = [NSMutableDictionary dictionary]; 1011 properties = [NSMutableDictionary dictionary];
867 objc_setAssociatedObject(window_, &kWindowPropertiesKey, 1012 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
868 properties, OBJC_ASSOCIATION_RETAIN); 1013 properties, OBJC_ASSOCIATION_RETAIN);
869 } 1014 }
870 return properties; 1015 return properties;
871 } 1016 }
872 1017
1018 void BridgedNativeWidget::SetDraggable(bool draggable) {
1019 [bridged_view_ setMouseDownCanMoveWindow:draggable];
1020 // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1021 // changes. Previously we tried adding an NSView and removing it, but for some
1022 // reason it required reposting the mouse-down event, and didn't always work.
1023 // Calling the below seems to be an effective solution.
1024 [window_ setMovableByWindowBackground:NO];
1025 [window_ setMovableByWindowBackground:YES];
1026 }
1027
873 } // namespace views 1028 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698