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

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 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 gfx::Size GetClientSizeForWindowSize(NSWindow* window, 73 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
72 const gfx::Size& window_size) { 74 const gfx::Size& window_size) {
73 NSRect frame_rect = 75 NSRect frame_rect =
74 NSMakeRect(0, 0, window_size.width(), window_size.height()); 76 NSMakeRect(0, 0, window_size.width(), window_size.height());
75 // Note gfx::Size will prevent dimensions going negative. They are allowed to 77 // Note gfx::Size will prevent dimensions going negative. They are allowed to
76 // be zero at this point, because Widget::GetMinimumSize() may later increase 78 // be zero at this point, because Widget::GetMinimumSize() may later increase
77 // the size. 79 // the size.
78 return gfx::Size([window contentRectForFrameRect:frame_rect].size); 80 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
79 } 81 }
80 82
83 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
84 id delegate = [[ns_event window] delegate];
85 return
86 [delegate
87 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
88 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
89 }
90
91 // Check if a mouse-down event should drag the window. If so, repost the event.
92 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
93 enum RepostState {
94 // Nothing reposted: hit-test new mouse-downs to see if they need to be
95 // ignored and reposted after changing draggability.
96 NONE,
97 // Expecting the next event to be the reposted event: let it go through.
98 EXPECTING_REPOST,
99 // If, while reposting, another mousedown was received: when the reposted
100 // event is seen, ignore it.
101 REPOST_CANCELLED,
102 };
103
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 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 if (WindowWantsMouseDownReposted(ns_event)) {
122 repost_state = EXPECTING_REPOST;
123 reposted_event_number = event_number;
124 CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
125 return nil;
126 }
127
128 return ns_event;
129 }
130
131 if (repost_state == EXPECTING_REPOST) {
132 // Call through so that the window is made non-draggable again.
133 WindowWantsMouseDownReposted(ns_event);
134
135 if (reposted_event_number == event_number) {
136 // Reposted event received.
137 repost_state = NONE;
138 return nil;
139 }
140
141 // We were expecting a repost, but since this is a new mouse-down, cancel
142 // reposting and allow event to continue as usual.
143 repost_state = REPOST_CANCELLED;
144 return ns_event;
145 }
146
147 DCHECK_EQ(REPOST_CANCELLED, repost_state);
148 if (reposted_event_number == event_number) {
149 // Reposting was cancelled, now that we've received the event, we don't
150 // expect to see it again.
151 repost_state = NONE;
152 return nil;
153 }
154
155 return ns_event;
156 }
157
158 // Support window caption/draggable regions.
159 // In AppKit, non-client regions are set by overriding
160 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
161 // installed and performs window moving when mouse-downs land in the area.
162 // In Views, non-client regions are determined via hit-tests when the event
163 // occurs.
164 // To bridge the two models, we monitor mouse-downs with
165 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
166 // events after window dragging is handled, so for mouse-downs that land on a
167 // draggable point, we cancel the event and repost it at the CGSessionEventTap
168 // level so that window dragging will be handled again.
169 void SetupDragEventMonitor() {
170 static id monitor = nil;
171 if (monitor)
172 return;
173
174 monitor = [NSEvent
175 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
176 handler:^NSEvent*(NSEvent* ns_event) {
177 return RepostEventIfHandledByWindow(ns_event);
178 }];
179 }
180
81 } // namespace 181 } // namespace
82 182
83 namespace views { 183 namespace views {
84 184
85 // static 185 // static
86 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( 186 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
87 NSWindow* window, 187 NSWindow* window,
88 const gfx::Size& content_size) { 188 const gfx::Size& content_size) {
89 NSRect content_rect = 189 NSRect content_rect =
90 NSMakeRect(0, 0, content_size.width(), content_size.height()); 190 NSMakeRect(0, 0, content_size.width(), content_size.height());
91 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 191 NSRect frame_rect = [window frameRectForContentRect:content_rect];
92 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 192 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
93 } 193 }
94 194
95 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) 195 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
96 : native_widget_mac_(parent), 196 : native_widget_mac_(parent),
97 focus_manager_(nullptr), 197 focus_manager_(nullptr),
98 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). 198 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
99 parent_(nullptr), 199 parent_(nullptr),
100 target_fullscreen_state_(false), 200 target_fullscreen_state_(false),
101 in_fullscreen_transition_(false), 201 in_fullscreen_transition_(false),
102 window_visible_(false), 202 window_visible_(false),
103 wants_to_be_visible_(false) { 203 wants_to_be_visible_(false) {
204 SetupDragEventMonitor();
104 DCHECK(parent); 205 DCHECK(parent);
105 window_delegate_.reset( 206 window_delegate_.reset(
106 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); 207 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
107 } 208 }
108 209
109 BridgedNativeWidget::~BridgedNativeWidget() { 210 BridgedNativeWidget::~BridgedNativeWidget() {
110 RemoveOrDestroyChildren(); 211 RemoveOrDestroyChildren();
111 DCHECK(child_windows_.empty()); 212 DCHECK(child_windows_.empty());
112 SetFocusManager(NULL); 213 SetFocusManager(NULL);
113 SetRootView(NULL); 214 SetRootView(NULL);
(...skipping 427 matching lines...) Expand 10 before | Expand all | Expand 10 after
541 if (is_key) { 642 if (is_key) {
542 widget->OnNativeFocus(); 643 widget->OnNativeFocus();
543 widget->GetFocusManager()->RestoreFocusedView(); 644 widget->GetFocusManager()->RestoreFocusedView();
544 } else { 645 } else {
545 widget->OnNativeBlur(); 646 widget->OnNativeBlur();
546 widget->GetFocusManager()->StoreFocusedView(true); 647 widget->GetFocusManager()->StoreFocusedView(true);
547 } 648 }
548 } 649 }
549 } 650 }
550 651
652 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
653 NSPoint location_in_window) {
654 if (!bridged_view_)
655 return false;
656
657 if ([bridged_view_ mouseDownCanMoveWindow]) {
658 // This is a re-post, the movement has already started, so we can make the
659 // window non-draggable again.
660 SetDraggable(false);
661 return false;
662 }
663
664 gfx::Point point(location_in_window.x,
665 NSHeight([window_ frame]) - location_in_window.y);
666 bool should_move_window =
667 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
668 HTCAPTION;
669
670 // Check that the point is not obscured by non-content NSViews.
671 for (NSView* subview : [[bridged_view_ superview] subviews]) {
672 if (subview == bridged_view_.get())
673 continue;
674
675 if (![subview mouseDownCanMoveWindow] &&
676 NSPointInRect(location_in_window, [subview frame])) {
677 should_move_window = false;
678 break;
679 }
680 }
681
682 if (!should_move_window)
683 return false;
684
685 // Make the window draggable, then return true to repost the event.
686 SetDraggable(true);
687 return true;
688 }
689
551 void BridgedNativeWidget::OnSizeConstraintsChanged() { 690 void BridgedNativeWidget::OnSizeConstraintsChanged() {
552 // Don't modify the size constraints or fullscreen collection behavior while 691 // Don't modify the size constraints or fullscreen collection behavior while
553 // in fullscreen or during a transition. OnFullscreenTransitionComplete will 692 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
554 // reset these after leaving fullscreen. 693 // reset these after leaving fullscreen.
555 if (target_fullscreen_state_ || in_fullscreen_transition_) 694 if (target_fullscreen_state_ || in_fullscreen_transition_)
556 return; 695 return;
557 696
558 Widget* widget = native_widget_mac()->GetWidget(); 697 Widget* widget = native_widget_mac()->GetWidget();
559 gfx::Size min_size = widget->GetMinimumSize(); 698 gfx::Size min_size = widget->GetMinimumSize();
560 gfx::Size max_size = widget->GetMaximumSize(); 699 gfx::Size max_size = widget->GetMaximumSize();
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after
879 NSMutableDictionary* properties = objc_getAssociatedObject( 1018 NSMutableDictionary* properties = objc_getAssociatedObject(
880 window_, &kWindowPropertiesKey); 1019 window_, &kWindowPropertiesKey);
881 if (!properties) { 1020 if (!properties) {
882 properties = [NSMutableDictionary dictionary]; 1021 properties = [NSMutableDictionary dictionary];
883 objc_setAssociatedObject(window_, &kWindowPropertiesKey, 1022 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
884 properties, OBJC_ASSOCIATION_RETAIN); 1023 properties, OBJC_ASSOCIATION_RETAIN);
885 } 1024 }
886 return properties; 1025 return properties;
887 } 1026 }
888 1027
1028 void BridgedNativeWidget::SetDraggable(bool draggable) {
1029 [bridged_view_ setMouseDownCanMoveWindow:draggable];
1030 // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1031 // changes. Previously we tried adding an NSView and removing it, but for some
1032 // reason it required reposting the mouse-down event, and didn't always work.
1033 // Calling the below seems to be an effective solution.
1034 [window_ setMovableByWindowBackground:NO];
1035 [window_ setMovableByWindowBackground:YES];
1036 }
1037
889 } // namespace views 1038 } // namespace views
OLDNEW
« no previous file with comments | « ui/views/cocoa/bridged_native_widget.h ('k') | ui/views/cocoa/bridged_native_widget_interactive_uitest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698