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

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: Change to single repost and [NSWindow setMovableByWindowBackground]. 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 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
70 gfx::Size GetClientSizeForWindowSize(NSWindow* window, 72 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
71 const gfx::Size& window_size) { 73 const gfx::Size& window_size) {
72 NSRect frame_rect = 74 NSRect frame_rect =
73 NSMakeRect(0, 0, window_size.width(), window_size.height()); 75 NSMakeRect(0, 0, window_size.width(), window_size.height());
74 // Note gfx::Size will prevent dimensions going negative. They are allowed to 76 // Note gfx::Size will prevent dimensions going negative. They are allowed to
75 // be zero at this point, because Widget::GetMinimumSize() may later increase 77 // be zero at this point, because Widget::GetMinimumSize() may later increase
76 // the size. 78 // the size.
77 return gfx::Size([window contentRectForFrameRect:frame_rect].size); 79 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
78 } 80 }
79 81
82 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
83 id delegate = [[ns_event window] delegate];
84 return
85 [delegate
86 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
87 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
88 }
89
90 // Check if a mouse-down event should drag the window. If so, repost the event.
91 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
92 // Which repost we're expecting to receive.
93 static bool expecting_repost = false;
94 // The event number of the reposted event. This let's us track whether an
95 // event is actually a repost since user-generated events have increasing
96 // event numbers.
97 static NSInteger reposted_event_number = -1;
tapted 2015/06/03 00:28:37 did we want to try this out with 0 instead of -1?
jackhou1 2015/06/03 04:04:44 Done.
98
99 CGEventRef cg_event = [ns_event CGEvent];
100 NSInteger event_number = [ns_event eventNumber];
101
102 // The logic here is a bit convoluted because we want to mitigate race
103 // conditions if somehow a different mouse-down occurs between reposts.
104 // Specifically, we want to avoid:
105 // - BridgedNativeWidget's draggability getting out of sync (e.g. it's
tapted 2015/06/03 00:28:37 nit: it's -> its
jackhou1 2015/06/03 04:04:44 This is actually an it's. Changed to "if it is".
tapted 2015/06/03 07:04:48 whoops! I must have read as "its draggability outs
106 // draggable outside of a repost cycle),
107 // - any repost loop.
108 if (reposted_event_number == event_number) {
109 // This is a reposted event.
110 if (expecting_repost) {
111 expecting_repost = false;
112 reposted_event_number = -1;
113 // This is the final repost, call through to make the window
114 // non-draggable.
115 WindowWantsMouseDownReposted(ns_event);
116 } else {
117 // Reposting was cancelled, now that we've received it, we don't expect
118 // to see it again.
119 reposted_event_number = -1;
120 }
121 return nil; // Ignore the event.
122 }
123
124 // This is a new event.
125 if (expecting_repost) {
126 // We were expecting a repost, but since this is a new mouse-down, cancel
127 // reposting and allow event to continue as usual.
128 expecting_repost = false;
129 // Call through so that the window is made non-draggable again.
130 WindowWantsMouseDownReposted(ns_event);
131 return ns_event;
132 }
133
134 // We're not in the middle of reposting, process this new event.
135 if (WindowWantsMouseDownReposted(ns_event)) {
136 expecting_repost = true;
137 reposted_event_number = [ns_event eventNumber];
138 CGEventPost(kCGSessionEventTap, cg_event);
139 return nil;
140 }
141
142 return ns_event;
143 }
144
145 // Support window caption/draggable regions.
146 // In AppKit, non-client regions are set by overriding
147 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
148 // installed and performs window moving when mouse-downs land in the area.
149 // In Views, non-client regions are determined via hit-tests when the event
150 // occurs.
151 // To bridge the two models, we monitor mouse-downs with
152 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
153 // events after window dragging is handled, so for mouse-downs that land on a
154 // draggable point, we cancel the event and repost it at the CGSessionEventTap
155 // level so that window dragging will be handled again.
156 void SetupDragEventMonitor() {
157 static id monitor = nil;
158 if (monitor)
159 return;
160
161 monitor = [NSEvent
162 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
163 handler:^NSEvent*(NSEvent* ns_event) {
164 return RepostEventIfHandledByWindow(ns_event);
165 }];
166 }
167
80 } // namespace 168 } // namespace
81 169
82 namespace views { 170 namespace views {
83 171
84 // static 172 // static
85 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( 173 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
86 NSWindow* window, 174 NSWindow* window,
87 const gfx::Size& content_size) { 175 const gfx::Size& content_size) {
88 NSRect content_rect = 176 NSRect content_rect =
89 NSMakeRect(0, 0, content_size.width(), content_size.height()); 177 NSMakeRect(0, 0, content_size.width(), content_size.height());
90 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 178 NSRect frame_rect = [window frameRectForContentRect:content_rect];
91 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 179 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
92 } 180 }
93 181
94 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) 182 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
95 : native_widget_mac_(parent), 183 : native_widget_mac_(parent),
96 focus_manager_(nullptr), 184 focus_manager_(nullptr),
97 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). 185 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
98 parent_(nullptr), 186 parent_(nullptr),
99 target_fullscreen_state_(false), 187 target_fullscreen_state_(false),
100 in_fullscreen_transition_(false), 188 in_fullscreen_transition_(false),
101 window_visible_(false), 189 window_visible_(false),
102 wants_to_be_visible_(false) { 190 wants_to_be_visible_(false) {
191 SetupDragEventMonitor();
103 DCHECK(parent); 192 DCHECK(parent);
104 window_delegate_.reset( 193 window_delegate_.reset(
105 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); 194 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
106 } 195 }
107 196
108 BridgedNativeWidget::~BridgedNativeWidget() { 197 BridgedNativeWidget::~BridgedNativeWidget() {
109 RemoveOrDestroyChildren(); 198 RemoveOrDestroyChildren();
110 DCHECK(child_windows_.empty()); 199 DCHECK(child_windows_.empty());
111 SetFocusManager(NULL); 200 SetFocusManager(NULL);
112 SetRootView(NULL); 201 SetRootView(NULL);
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after
525 if (is_key) { 614 if (is_key) {
526 widget->OnNativeFocus(); 615 widget->OnNativeFocus();
527 widget->GetFocusManager()->RestoreFocusedView(); 616 widget->GetFocusManager()->RestoreFocusedView();
528 } else { 617 } else {
529 widget->OnNativeBlur(); 618 widget->OnNativeBlur();
530 widget->GetFocusManager()->StoreFocusedView(true); 619 widget->GetFocusManager()->StoreFocusedView(true);
531 } 620 }
532 } 621 }
533 } 622 }
534 623
624 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
625 NSPoint location_in_window) {
626 if (!bridged_view_)
627 return false;
628
629 if ([bridged_view_ mouseDownCanMoveWindow]) {
630 // This is a re-post, the movement has already started, so we can make the
631 // window non-draggable again.
632 SetDraggable(false);
633 return false;
634 }
635
636 gfx::Point point(location_in_window.x,
637 NSHeight([window_ frame]) - location_in_window.y);
638 bool should_move_window =
639 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
640 HTCAPTION;
641
642 if (!should_move_window)
643 return false;
644
645 // Make the window draggable, then return true to repost the event.
646 SetDraggable(true);
647 return true;
648 }
649
535 void BridgedNativeWidget::OnSizeConstraintsChanged() { 650 void BridgedNativeWidget::OnSizeConstraintsChanged() {
536 // Don't modify the size constraints or fullscreen collection behavior while 651 // Don't modify the size constraints or fullscreen collection behavior while
537 // in fullscreen or during a transition. OnFullscreenTransitionComplete will 652 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
538 // reset these after leaving fullscreen. 653 // reset these after leaving fullscreen.
539 if (target_fullscreen_state_ || in_fullscreen_transition_) 654 if (target_fullscreen_state_ || in_fullscreen_transition_)
540 return; 655 return;
541 656
542 Widget* widget = native_widget_mac()->GetWidget(); 657 Widget* widget = native_widget_mac()->GetWidget();
543 gfx::Size min_size = widget->GetMinimumSize(); 658 gfx::Size min_size = widget->GetMinimumSize();
544 gfx::Size max_size = widget->GetMaximumSize(); 659 gfx::Size max_size = widget->GetMaximumSize();
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after
863 NSMutableDictionary* properties = objc_getAssociatedObject( 978 NSMutableDictionary* properties = objc_getAssociatedObject(
864 window_, &kWindowPropertiesKey); 979 window_, &kWindowPropertiesKey);
865 if (!properties) { 980 if (!properties) {
866 properties = [NSMutableDictionary dictionary]; 981 properties = [NSMutableDictionary dictionary];
867 objc_setAssociatedObject(window_, &kWindowPropertiesKey, 982 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
868 properties, OBJC_ASSOCIATION_RETAIN); 983 properties, OBJC_ASSOCIATION_RETAIN);
869 } 984 }
870 return properties; 985 return properties;
871 } 986 }
872 987
988 void BridgedNativeWidget::SetDraggable(bool draggable) {
989 [bridged_view_ setMouseDownCanMoveWindow:draggable];
990 // AppKit will not update its cache of mouseDownCanMoveWindow unless something
991 // changes. Previously we tried adding an NSView and removing it, but for some
992 // reason it required reposting the mouse-down event, and didn't always work.
993 // Calling the below seems to be an effective solution.
994 [window_ setMovableByWindowBackground:NO];
jackhou1 2015/06/02 06:33:09 I'm not sure exactly why it wasn't working for bro
tapted 2015/06/03 00:28:37 Nice!
995 [window_ setMovableByWindowBackground:YES];
996 }
997
873 } // namespace views 998 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698