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

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, 7 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/cocoa_mouse_capture.h" 23 #import "ui/views/cocoa/cocoa_mouse_capture.h"
22 #import "ui/views/cocoa/bridged_content_view.h" 24 #import "ui/views/cocoa/bridged_content_view.h"
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 gfx::Size GetClientSizeForWindowSize(NSWindow* window, 71 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
70 const gfx::Size& window_size) { 72 const gfx::Size& window_size) {
71 NSRect frame_rect = 73 NSRect frame_rect =
72 NSMakeRect(0, 0, window_size.width(), window_size.height()); 74 NSMakeRect(0, 0, window_size.width(), window_size.height());
73 // Note gfx::Size will prevent dimensions going negative. They are allowed to 75 // Note gfx::Size will prevent dimensions going negative. They are allowed to
74 // be zero at this point, because Widget::GetMinimumSize() may later increase 76 // be zero at this point, because Widget::GetMinimumSize() may later increase
75 // the size. 77 // the size.
76 return gfx::Size([window contentRectForFrameRect:frame_rect].size); 78 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
77 } 79 }
78 80
81 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
82 id delegate = [[ns_event window] delegate];
83 return
84 [delegate
85 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
86 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
87 }
88
89 // Check if a mouse-down event should drag the window. If so, repost the event
90 // twice. It doesn't seem to work the first time it's reposted.
91 NSEvent* DoubleRepostEventIfHandledByWindow(NSEvent* ns_event) {
92 // Which repost we're expecting to receive.
93 static int expected_repost_count = 0;
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;
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 mousedown occurs between reposts.
104 // Specifically, we want to avoid:
105 // - BridgedNativeWidget's draggability getting out of sync (e.g. it's
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 (expected_repost_count == 1) {
111 // There has been no intermediate mouse-down, repost the event again.
112 expected_repost_count = 2;
113 CGEventPost(kCGSessionEventTap, cg_event);
114 } else if (expected_repost_count == 2) {
115 expected_repost_count = 0;
116 // This is the final repost, call through to make the window
117 // non-draggable.
118 WindowWantsMouseDownReposted(ns_event);
119 }
tapted 2015/05/22 04:03:12 else { NOTREACHED(); }? (i.e. we should never see
jackhou1 2015/05/25 04:50:19 In the if (expected_repost_count > 0) block below,
120 return nil; // Ignore the event.
121 } else {
tapted 2015/05/22 04:03:12 nit: no else after return
jackhou1 2015/05/25 04:50:19 Done.
122 // This is a new event.
123 if (expected_repost_count > 0) {
124 // We were expecting a repost, but since this is a new mouse-down, cancel
125 // reposting and allow event to continue as usual.
126 expected_repost_count = 0;
127 // Call through so that the window is made non-draggable again.
128 WindowWantsMouseDownReposted(ns_event);
129 return ns_event;
130 } else {
tapted 2015/05/22 04:03:12 no else after return
jackhou1 2015/05/25 04:50:19 Done.
131 // We're not in the middle of reposting, process this new event.
132 if (WindowWantsMouseDownReposted(ns_event)) {
133 expected_repost_count = 1;
134 reposted_event_number = [ns_event eventNumber];
135 CGEventPost(kCGSessionEventTap, cg_event);
136 return nil;
137 }
138 return ns_event;
139 }
140 }
141 }
142
143 // Support window caption/draggable regions.
144 // In AppKit, non-client regions are set by overriding
145 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
146 // installed and performs window moving when mouse-downs land in the area.
147 // In Views, non-client regions are determined via hit-tests when the event
148 // occurs.
149 // To bridge the two models, we monitor mouse-downs with
150 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
151 // events after window dragging is handled, so for mouse-downs that land on a
152 // draggable point, we cancel the event and repost it at the CGSessionEventTap
153 // level so that window dragging will be handled again.
154 void SetupDragEventMonitor() {
155 static id g_event_monitor = nil;
tapted 2015/05/22 04:03:12 nit: no `g_` for local statics. I'd maybe just cal
jackhou1 2015/05/25 04:50:19 Done.
156 if (g_event_monitor)
157 return;
158
159 NSEvent* (^monitor_callback)(NSEvent* ns_event);
tapted 2015/05/22 04:03:12 nit: move this into the function call (just to hid
jackhou1 2015/05/25 04:50:19 Done.
160 monitor_callback = ^NSEvent*(NSEvent* ns_event) {
161 return DoubleRepostEventIfHandledByWindow(ns_event);
162 };
163
164 g_event_monitor =
165 [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
166 handler:monitor_callback];
167 }
168
79 } // namespace 169 } // namespace
80 170
81 namespace views { 171 namespace views {
82 172
83 // static 173 // static
84 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( 174 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
85 NSWindow* window, 175 NSWindow* window,
86 const gfx::Size& content_size) { 176 const gfx::Size& content_size) {
87 NSRect content_rect = 177 NSRect content_rect =
88 NSMakeRect(0, 0, content_size.width(), content_size.height()); 178 NSMakeRect(0, 0, content_size.width(), content_size.height());
89 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 179 NSRect frame_rect = [window frameRectForContentRect:content_rect];
90 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 180 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
91 } 181 }
92 182
93 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) 183 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
94 : native_widget_mac_(parent), 184 : native_widget_mac_(parent),
95 focus_manager_(nullptr), 185 focus_manager_(nullptr),
96 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). 186 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
97 parent_(nullptr), 187 parent_(nullptr),
98 target_fullscreen_state_(false), 188 target_fullscreen_state_(false),
99 in_fullscreen_transition_(false), 189 in_fullscreen_transition_(false),
100 window_visible_(false), 190 window_visible_(false),
101 wants_to_be_visible_(false) { 191 wants_to_be_visible_(false) {
192 SetupDragEventMonitor();
102 DCHECK(parent); 193 DCHECK(parent);
103 window_delegate_.reset( 194 window_delegate_.reset(
104 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); 195 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
105 } 196 }
106 197
107 BridgedNativeWidget::~BridgedNativeWidget() { 198 BridgedNativeWidget::~BridgedNativeWidget() {
108 RemoveOrDestroyChildren(); 199 RemoveOrDestroyChildren();
109 DCHECK(child_windows_.empty()); 200 DCHECK(child_windows_.empty());
110 SetFocusManager(NULL); 201 SetFocusManager(NULL);
111 SetRootView(NULL); 202 SetRootView(NULL);
(...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after
515 if (is_key) { 606 if (is_key) {
516 widget->OnNativeFocus(); 607 widget->OnNativeFocus();
517 widget->GetFocusManager()->RestoreFocusedView(); 608 widget->GetFocusManager()->RestoreFocusedView();
518 } else { 609 } else {
519 widget->OnNativeBlur(); 610 widget->OnNativeBlur();
520 widget->GetFocusManager()->StoreFocusedView(true); 611 widget->GetFocusManager()->StoreFocusedView(true);
521 } 612 }
522 } 613 }
523 } 614 }
524 615
616 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
617 NSPoint location_in_window) {
618 if (!bridged_view_)
619 return false;
620
621 if ([bridged_view_ mouseDownCanMoveWindow]) {
622 // This is a re-post, the movement has already started, so we can make the
623 // window non-draggable again.
624 SetDraggable(false);
625 return false;
626 }
627
628 gfx::Point point(location_in_window.x,
629 NSHeight([window_ frame]) - location_in_window.y);
630 bool should_move_window =
631 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
632 HTCAPTION;
633
634 if (!should_move_window)
635 return false;
636
637 // Make the window draggable, then return true to repost the event.
638 SetDraggable(true);
639 return true;
640 }
641
525 void BridgedNativeWidget::OnSizeConstraintsChanged() { 642 void BridgedNativeWidget::OnSizeConstraintsChanged() {
526 NSWindow* window = ns_window(); 643 NSWindow* window = ns_window();
527 Widget* widget = native_widget_mac()->GetWidget(); 644 Widget* widget = native_widget_mac()->GetWidget();
528 gfx::Size min_size = widget->GetMinimumSize(); 645 gfx::Size min_size = widget->GetMinimumSize();
529 gfx::Size max_size = widget->GetMaximumSize(); 646 gfx::Size max_size = widget->GetMaximumSize();
530 bool is_resizable = widget->widget_delegate()->CanResize(); 647 bool is_resizable = widget->widget_delegate()->CanResize();
531 bool shows_resize_controls = 648 bool shows_resize_controls =
532 is_resizable && (min_size.IsEmpty() || min_size != max_size); 649 is_resizable && (min_size.IsEmpty() || min_size != max_size);
533 bool shows_fullscreen_controls = 650 bool shows_fullscreen_controls =
534 is_resizable && widget->widget_delegate()->CanMaximize(); 651 is_resizable && widget->widget_delegate()->CanMaximize();
(...skipping 313 matching lines...) Expand 10 before | Expand all | Expand 10 after
848 NSMutableDictionary* properties = objc_getAssociatedObject( 965 NSMutableDictionary* properties = objc_getAssociatedObject(
849 window_, &kWindowPropertiesKey); 966 window_, &kWindowPropertiesKey);
850 if (!properties) { 967 if (!properties) {
851 properties = [NSMutableDictionary dictionary]; 968 properties = [NSMutableDictionary dictionary];
852 objc_setAssociatedObject(window_, &kWindowPropertiesKey, 969 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
853 properties, OBJC_ASSOCIATION_RETAIN); 970 properties, OBJC_ASSOCIATION_RETAIN);
854 } 971 }
855 return properties; 972 return properties;
856 } 973 }
857 974
975 void BridgedNativeWidget::SetDraggable(bool draggable) {
976 [bridged_view_ setMouseDownCanMoveWindow:draggable];
977 // AppKit will not update it's cache of mouseDownCanMoveWindow unless
tapted 2015/05/22 04:03:12 nit: it's -> its
jackhou1 2015/05/25 04:50:19 Done.
978 // something changes.
979 base::scoped_nsobject<NSView> temp_view(
980 [[NSView alloc] initWithFrame:[bridged_view_ bounds]]);
981 [bridged_view_ addSubview:temp_view];
982 [temp_view removeFromSuperview];
983 }
984
858 } // namespace views 985 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698