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

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: Sync and rebase 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"
13 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" 14 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
15 #include "ui/base/hit_test.h"
14 #include "ui/base/ime/input_method.h" 16 #include "ui/base/ime/input_method.h"
15 #include "ui/base/ime/input_method_factory.h" 17 #include "ui/base/ime/input_method_factory.h"
16 #include "ui/base/ui_base_switches_util.h" 18 #include "ui/base/ui_base_switches_util.h"
17 #include "ui/gfx/display.h" 19 #include "ui/gfx/display.h"
18 #include "ui/gfx/geometry/dip_util.h" 20 #include "ui/gfx/geometry/dip_util.h"
19 #import "ui/gfx/mac/coordinate_conversion.h" 21 #import "ui/gfx/mac/coordinate_conversion.h"
20 #import "ui/gfx/mac/nswindow_frame_controls.h" 22 #import "ui/gfx/mac/nswindow_frame_controls.h"
21 #include "ui/gfx/screen.h" 23 #include "ui/gfx/screen.h"
22 #import "ui/views/cocoa/bridged_content_view.h" 24 #import "ui/views/cocoa/bridged_content_view.h"
23 #import "ui/views/cocoa/cocoa_mouse_capture.h" 25 #import "ui/views/cocoa/cocoa_mouse_capture.h"
(...skipping 16 matching lines...) Expand all
40 @end 42 @end
41 43
42 @implementation ViewsCompositorSuperview 44 @implementation ViewsCompositorSuperview
43 - (NSView*)hitTest:(NSPoint)aPoint { 45 - (NSView*)hitTest:(NSPoint)aPoint {
44 return nil; 46 return nil;
45 } 47 }
46 @end 48 @end
47 49
48 namespace { 50 namespace {
49 51
52 enum RepostState {
53 // Nothing reposted: hit-test new mouse-downs to see if they need to be
54 // ignored and reposted after changing draggability.
55 NONE,
56 // Expecting the next event to be the reposted event: let it go through.
57 EXPECTING_REPOST,
58 // If, while reposting, another mousedown was received: when the reposted
59 // event is seen, ignore it.
60 REPOST_CANCELLED,
61 };
62
50 int kWindowPropertiesKey; 63 int kWindowPropertiesKey;
51 64
52 float GetDeviceScaleFactorFromView(NSView* view) { 65 float GetDeviceScaleFactorFromView(NSView* view) {
53 gfx::Display display = 66 gfx::Display display =
54 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view); 67 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
55 DCHECK(display.is_valid()); 68 DCHECK(display.is_valid());
56 return display.device_scale_factor(); 69 return display.device_scale_factor();
57 } 70 }
58 71
59 // Returns true if bounds passed to window in SetBounds should be treated as 72 // Returns true if bounds passed to window in SetBounds should be treated as
(...skipping 11 matching lines...) Expand all
71 gfx::Size GetClientSizeForWindowSize(NSWindow* window, 84 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
72 const gfx::Size& window_size) { 85 const gfx::Size& window_size) {
73 NSRect frame_rect = 86 NSRect frame_rect =
74 NSMakeRect(0, 0, window_size.width(), window_size.height()); 87 NSMakeRect(0, 0, window_size.width(), window_size.height());
75 // Note gfx::Size will prevent dimensions going negative. They are allowed to 88 // Note gfx::Size will prevent dimensions going negative. They are allowed to
76 // be zero at this point, because Widget::GetMinimumSize() may later increase 89 // be zero at this point, because Widget::GetMinimumSize() may later increase
77 // the size. 90 // the size.
78 return gfx::Size([window contentRectForFrameRect:frame_rect].size); 91 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
79 } 92 }
80 93
94 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
95 id delegate = [[ns_event window] delegate];
96 return
97 [delegate
98 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
99 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
100 }
101
102 // Check if a mouse-down event should drag the window. If so, repost the event.
103 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
104 // Which repost we're expecting to receive.
105 static RepostState repost_state = NONE;
106 // The event number of the reposted event. This let's us track whether an
107 // event is actually the repost since user-generated events have increasing
108 // event numbers. This is only valid while |repost_state != NONE|.
109 static NSInteger reposted_event_number;
110
111 CGEventRef cg_event = [ns_event CGEvent];
112 NSInteger event_number = [ns_event eventNumber];
113
114 // The logic here is a bit convoluted because we want to mitigate race
115 // conditions if somehow a different mouse-down occurs between reposts.
116 // Specifically, we want to avoid:
117 // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is
118 // draggable outside of a repost cycle),
119 // - any repost loop.
120
121 if (repost_state == NONE) {
122 // We're not in the middle of reposting, process this new event.
123 if (WindowWantsMouseDownReposted(ns_event)) {
124 repost_state = EXPECTING_REPOST;
125 reposted_event_number = [ns_event eventNumber];
126 CGEventPost(kCGSessionEventTap, cg_event);
127 return nil;
128 }
129
130 return ns_event;
131 }
132
133 if (repost_state == EXPECTING_REPOST) {
134 // Call through so that the window is made non-draggable again.
135 WindowWantsMouseDownReposted(ns_event);
136
137 if (reposted_event_number == event_number) {
138 // Reposted event received.
139 repost_state = NONE;
140 return nil;
141 }
142
143 // We were expecting a repost, but since this is a new mouse-down, cancel
144 // reposting and allow event to continue as usual.
145 repost_state = REPOST_CANCELLED;
146 return ns_event;
147 }
148
149 if (repost_state == REPOST_CANCELLED) {
150 if (reposted_event_number == event_number) {
151 // Reposting was cancelled, now that we've received the event, we don't
152 // expect to see it again.
153 repost_state = NONE;
154 return nil;
155 }
156
157 return ns_event;
158 }
159
160 NOTREACHED();
161 return ns_event;
162 }
163
164 // Support window caption/draggable regions.
165 // In AppKit, non-client regions are set by overriding
166 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
167 // installed and performs window moving when mouse-downs land in the area.
168 // In Views, non-client regions are determined via hit-tests when the event
169 // occurs.
170 // To bridge the two models, we monitor mouse-downs with
171 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
172 // events after window dragging is handled, so for mouse-downs that land on a
173 // draggable point, we cancel the event and repost it at the CGSessionEventTap
174 // level so that window dragging will be handled again.
175 void SetupDragEventMonitor() {
176 static id monitor = nil;
177 if (monitor)
178 return;
179
180 monitor = [NSEvent
181 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
182 handler:^NSEvent*(NSEvent* ns_event) {
183 return RepostEventIfHandledByWindow(ns_event);
184 }];
185 }
186
81 } // namespace 187 } // namespace
82 188
83 namespace views { 189 namespace views {
84 190
85 // static 191 // static
86 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( 192 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
87 NSWindow* window, 193 NSWindow* window,
88 const gfx::Size& content_size) { 194 const gfx::Size& content_size) {
89 NSRect content_rect = 195 NSRect content_rect =
90 NSMakeRect(0, 0, content_size.width(), content_size.height()); 196 NSMakeRect(0, 0, content_size.width(), content_size.height());
91 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 197 NSRect frame_rect = [window frameRectForContentRect:content_rect];
92 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 198 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
93 } 199 }
94 200
95 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) 201 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
96 : native_widget_mac_(parent), 202 : native_widget_mac_(parent),
97 focus_manager_(nullptr), 203 focus_manager_(nullptr),
98 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). 204 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
99 parent_(nullptr), 205 parent_(nullptr),
100 target_fullscreen_state_(false), 206 target_fullscreen_state_(false),
101 in_fullscreen_transition_(false), 207 in_fullscreen_transition_(false),
102 window_visible_(false), 208 window_visible_(false),
103 wants_to_be_visible_(false) { 209 wants_to_be_visible_(false) {
210 SetupDragEventMonitor();
104 DCHECK(parent); 211 DCHECK(parent);
105 window_delegate_.reset( 212 window_delegate_.reset(
106 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); 213 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
107 } 214 }
108 215
109 BridgedNativeWidget::~BridgedNativeWidget() { 216 BridgedNativeWidget::~BridgedNativeWidget() {
110 RemoveOrDestroyChildren(); 217 RemoveOrDestroyChildren();
111 DCHECK(child_windows_.empty()); 218 DCHECK(child_windows_.empty());
112 SetFocusManager(NULL); 219 SetFocusManager(NULL);
113 SetRootView(NULL); 220 SetRootView(NULL);
(...skipping 427 matching lines...) Expand 10 before | Expand all | Expand 10 after
541 if (is_key) { 648 if (is_key) {
542 widget->OnNativeFocus(); 649 widget->OnNativeFocus();
543 widget->GetFocusManager()->RestoreFocusedView(); 650 widget->GetFocusManager()->RestoreFocusedView();
544 } else { 651 } else {
545 widget->OnNativeBlur(); 652 widget->OnNativeBlur();
546 widget->GetFocusManager()->StoreFocusedView(true); 653 widget->GetFocusManager()->StoreFocusedView(true);
547 } 654 }
548 } 655 }
549 } 656 }
550 657
658 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
659 NSPoint location_in_window) {
660 if (!bridged_view_)
661 return false;
662
663 if ([bridged_view_ mouseDownCanMoveWindow]) {
664 // This is a re-post, the movement has already started, so we can make the
665 // window non-draggable again.
666 SetDraggable(false);
667 return false;
668 }
669
670 gfx::Point point(location_in_window.x,
671 NSHeight([window_ frame]) - location_in_window.y);
672 bool should_move_window =
673 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
674 HTCAPTION;
675
676 // Check that the point is not obscured by non-content NSViews.
677 for (NSView* subview : [[bridged_view_ superview] subviews]) {
678 if (subview == bridged_view_.get())
679 continue;
680
681 if (![subview mouseDownCanMoveWindow] &&
682 NSPointInRect(location_in_window, [subview frame])) {
683 should_move_window = false;
684 break;
685 }
686 }
687
688 if (!should_move_window)
689 return false;
690
691 // Make the window draggable, then return true to repost the event.
692 SetDraggable(true);
693 return true;
694 }
695
551 void BridgedNativeWidget::OnSizeConstraintsChanged() { 696 void BridgedNativeWidget::OnSizeConstraintsChanged() {
552 // Don't modify the size constraints or fullscreen collection behavior while 697 // Don't modify the size constraints or fullscreen collection behavior while
553 // in fullscreen or during a transition. OnFullscreenTransitionComplete will 698 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
554 // reset these after leaving fullscreen. 699 // reset these after leaving fullscreen.
555 if (target_fullscreen_state_ || in_fullscreen_transition_) 700 if (target_fullscreen_state_ || in_fullscreen_transition_)
556 return; 701 return;
557 702
558 Widget* widget = native_widget_mac()->GetWidget(); 703 Widget* widget = native_widget_mac()->GetWidget();
559 gfx::Size min_size = widget->GetMinimumSize(); 704 gfx::Size min_size = widget->GetMinimumSize();
560 gfx::Size max_size = widget->GetMaximumSize(); 705 gfx::Size max_size = widget->GetMaximumSize();
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after
879 NSMutableDictionary* properties = objc_getAssociatedObject( 1024 NSMutableDictionary* properties = objc_getAssociatedObject(
880 window_, &kWindowPropertiesKey); 1025 window_, &kWindowPropertiesKey);
881 if (!properties) { 1026 if (!properties) {
882 properties = [NSMutableDictionary dictionary]; 1027 properties = [NSMutableDictionary dictionary];
883 objc_setAssociatedObject(window_, &kWindowPropertiesKey, 1028 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
884 properties, OBJC_ASSOCIATION_RETAIN); 1029 properties, OBJC_ASSOCIATION_RETAIN);
885 } 1030 }
886 return properties; 1031 return properties;
887 } 1032 }
888 1033
1034 void BridgedNativeWidget::SetDraggable(bool draggable) {
1035 [bridged_view_ setMouseDownCanMoveWindow:draggable];
1036 // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1037 // changes. Previously we tried adding an NSView and removing it, but for some
1038 // reason it required reposting the mouse-down event, and didn't always work.
1039 // Calling the below seems to be an effective solution.
1040 [window_ setMovableByWindowBackground:NO];
1041 [window_ setMovableByWindowBackground:YES];
1042 }
1043
889 } // namespace views 1044 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698