osx: Implement draggable region with mouseDownCanMoveWindow

Previously we implemented draggable region by tracking mouse position,
it is buggy and causing some problems. But it is interesting that until
this didn't cause troubles until recently.
This commit is contained in:
Cheng Zhao 2015-10-21 07:33:43 +08:00
parent c928894627
commit d092c6acc9
4 changed files with 116 additions and 104 deletions

View file

@ -43,26 +43,6 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay);
namespace atom { namespace atom {
namespace {
// Convert draggable regions in raw format to SkRegion format. Caller is
// responsible for deleting the returned SkRegion instance.
scoped_ptr<SkRegion> DraggableRegionsToSkRegion(
const std::vector<DraggableRegion>& regions) {
scoped_ptr<SkRegion> sk_region(new SkRegion);
for (const DraggableRegion& region : regions) {
sk_region->op(
region.bounds.x(),
region.bounds.y(),
region.bounds.right(),
region.bounds.bottom(),
region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
}
return sk_region.Pass();
}
} // namespace
NativeWindow::NativeWindow( NativeWindow::NativeWindow(
brightray::InspectableWebContents* inspectable_web_contents, brightray::InspectableWebContents* inspectable_web_contents,
const mate::Dictionary& options) const mate::Dictionary& options)
@ -480,6 +460,20 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand(
OnExecuteWindowsCommand(command)); OnExecuteWindowsCommand(command));
} }
scoped_ptr<SkRegion> NativeWindow::DraggableRegionsToSkRegion(
const std::vector<DraggableRegion>& regions) {
scoped_ptr<SkRegion> sk_region(new SkRegion);
for (const DraggableRegion& region : regions) {
sk_region->op(
region.bounds.x(),
region.bounds.y(),
region.bounds.right(),
region.bounds.bottom(),
region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
}
return sk_region.Pass();
}
void NativeWindow::RenderViewCreated( void NativeWindow::RenderViewCreated(
content::RenderViewHost* render_view_host) { content::RenderViewHost* render_view_host) {
if (!transparent_) if (!transparent_)

View file

@ -241,10 +241,19 @@ class NativeWindow : public base::SupportsUserData,
NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, NativeWindow(brightray::InspectableWebContents* inspectable_web_contents,
const mate::Dictionary& options); const mate::Dictionary& options);
// Convert draggable regions in raw format to SkRegion format. Caller is
// responsible for deleting the returned SkRegion instance.
scoped_ptr<SkRegion> DraggableRegionsToSkRegion(
const std::vector<DraggableRegion>& regions);
// Converts between content size to window size. // Converts between content size to window size.
virtual gfx::Size ContentSizeToWindowSize(const gfx::Size& size) = 0; virtual gfx::Size ContentSizeToWindowSize(const gfx::Size& size) = 0;
virtual gfx::Size WindowSizeToContentSize(const gfx::Size& size) = 0; virtual gfx::Size WindowSizeToContentSize(const gfx::Size& size) = 0;
// Called when the window needs to update its draggable region.
virtual void UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions);
// content::WebContentsObserver: // content::WebContentsObserver:
void RenderViewCreated(content::RenderViewHost* render_view_host) override; void RenderViewCreated(content::RenderViewHost* render_view_host) override;
void BeforeUnloadDialogCancelled() override; void BeforeUnloadDialogCancelled() override;
@ -252,10 +261,6 @@ class NativeWindow : public base::SupportsUserData,
bool OnMessageReceived(const IPC::Message& message) override; bool OnMessageReceived(const IPC::Message& message) override;
private: private:
// Called when the window needs to update its draggable region.
void UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions);
// Schedule a notification unresponsive event. // Schedule a notification unresponsive event.
void ScheduleUnresponsiveEvent(int ms); void ScheduleUnresponsiveEvent(int ms);

View file

@ -71,12 +71,10 @@ class NativeWindowMac : public NativeWindow {
void SetVisibleOnAllWorkspaces(bool visible) override; void SetVisibleOnAllWorkspaces(bool visible) override;
bool IsVisibleOnAllWorkspaces() override; bool IsVisibleOnAllWorkspaces() override;
// Returns true if |point| in local Cocoa coordinate system falls within // Refresh the DraggableRegion views.
// the draggable region. void UpdateDraggableRegionViews() {
bool IsWithinDraggableRegion(NSPoint point) const; UpdateDraggableRegionViews(draggable_regions_);
}
// Called to handle a mouse event.
void HandleMouseEvent(NSEvent* event);
protected: protected:
// NativeWindow: // NativeWindow:
@ -84,17 +82,24 @@ class NativeWindowMac : public NativeWindow {
content::WebContents*, content::WebContents*,
const content::NativeWebKeyboardEvent&) override; const content::NativeWebKeyboardEvent&) override;
// Return a vector of non-draggable regions that fill a window of size
// |width| by |height|, but leave gaps where the window should be draggable.
std::vector<gfx::Rect> CalculateNonDraggableRegions(
const std::vector<DraggableRegion>& regions, int width, int height);
private: private:
// NativeWindow: // NativeWindow:
gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override; gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override;
gfx::Size WindowSizeToContentSize(const gfx::Size& size) override; gfx::Size WindowSizeToContentSize(const gfx::Size& size) override;
void UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions) override;
void InstallView(); void InstallView();
void UninstallView(); void UninstallView();
// Install the drag view, which will cover the whole window and decides // Install the drag view, which will cover the whole window and decides
// whehter we can drag. // whehter we can drag.
void InstallDraggableRegionView(); void UpdateDraggableRegionViews(const std::vector<DraggableRegion>& regions);
base::scoped_nsobject<AtomNSWindow> window_; base::scoped_nsobject<AtomNSWindow> window_;
base::scoped_nsobject<AtomNSWindowDelegate> window_delegate_; base::scoped_nsobject<AtomNSWindowDelegate> window_delegate_;
@ -102,6 +107,8 @@ class NativeWindowMac : public NativeWindow {
// The view that will fill the whole frameless window. // The view that will fill the whole frameless window.
base::scoped_nsobject<FullSizeContentView> content_view_; base::scoped_nsobject<FullSizeContentView> content_view_;
std::vector<DraggableRegion> draggable_regions_;
bool is_kiosk_; bool is_kiosk_;
NSInteger attention_request_id_; // identifier from requestUserAttention NSInteger attention_request_id_; // identifier from requestUserAttention
@ -109,10 +116,6 @@ class NativeWindowMac : public NativeWindow {
// The presentation options before entering kiosk mode. // The presentation options before entering kiosk mode.
NSApplicationPresentationOptions kiosk_options_; NSApplicationPresentationOptions kiosk_options_;
// Mouse location since the last mouse event, in screen coordinates. This is
// used in custom drag to compute the window movement.
NSPoint last_mouse_offset_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); DISALLOW_COPY_AND_ASSIGN(NativeWindowMac);
}; };

View file

@ -19,6 +19,7 @@
#include "content/public/browser/render_view_host.h" #include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/render_widget_host_view.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "ui/gfx/skia_util.h"
namespace { namespace {
@ -146,6 +147,7 @@ bool ScopedDisableResize::disable_resize_ = false;
} }
- (void)windowDidResize:(NSNotification*)notification { - (void)windowDidResize:(NSNotification*)notification {
shell_->UpdateDraggableRegionViews();
shell_->NotifyWindowResize(); shell_->NotifyWindowResize();
} }
@ -257,43 +259,23 @@ bool ScopedDisableResize::disable_resize_ = false;
@end @end
@interface ControlRegionView : NSView { @interface ControlRegionView : NSView
@private
atom::NativeWindowMac* shellWindow_; // Weak; owns self.
}
@end @end
@implementation ControlRegionView @implementation ControlRegionView
- (id)initWithShellWindow:(atom::NativeWindowMac*)shellWindow {
if ((self = [super init]))
shellWindow_ = shellWindow;
return self;
}
- (BOOL)mouseDownCanMoveWindow { - (BOOL)mouseDownCanMoveWindow {
return NO; return NO;
} }
- (NSView*)hitTest:(NSPoint)aPoint { - (NSView*)hitTest:(NSPoint)aPoint {
if (!shellWindow_->IsWithinDraggableRegion(aPoint)) {
return nil; return nil;
} }
return self;
}
- (void)mouseDown:(NSEvent*)event { @end
shellWindow_->HandleMouseEvent(event);
}
- (void)mouseDragged:(NSEvent*)event {
shellWindow_->HandleMouseEvent(event);
}
- (BOOL)acceptsFirstMouse:(NSEvent*)event {
return YES;
}
@interface NSView (WebContentsView)
- (void)setMouseDownCanMoveWindow:(BOOL)can_move;
@end @end
@interface AtomProgressBar : NSProgressIndicator @interface AtomProgressBar : NSProgressIndicator
@ -439,11 +421,6 @@ NativeWindowMac::NativeWindowMac(
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
InstallView(); InstallView();
// Install the DraggableRegionView if it is forced to use draggable regions
// for normal window.
if (has_frame() && force_using_draggable_region())
InstallDraggableRegionView();
} }
NativeWindowMac::~NativeWindowMac() { NativeWindowMac::~NativeWindowMac() {
@ -746,36 +723,6 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() {
return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces; return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces;
} }
bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const {
if (!draggable_region())
return false;
if (!web_contents())
return false;
NSView* webView = web_contents()->GetNativeView();
NSInteger webViewHeight = NSHeight([webView bounds]);
// |draggable_region_| is stored in local platform-indepdent coordiate system
// while |point| is in local Cocoa coordinate system. Do the conversion
// to match these two.
return draggable_region()->contains(point.x, webViewHeight - point.y);
}
void NativeWindowMac::HandleMouseEvent(NSEvent* event) {
NSPoint eventLoc = [event locationInWindow];
NSRect mouseRect = [window_ convertRectToScreen:NSMakeRect(eventLoc.x, eventLoc.y, 0, 0)];
NSPoint current_mouse_location = mouseRect.origin;
if ([event type] == NSLeftMouseDown) {
NSPoint frame_origin = [window_ frame].origin;
last_mouse_offset_ = NSMakePoint(
frame_origin.x - current_mouse_location.x,
frame_origin.y - current_mouse_location.y);
} else if ([event type] == NSLeftMouseDragged) {
[window_ setFrameOrigin:NSMakePoint(
current_mouse_location.x + last_mouse_offset_.x,
current_mouse_location.y + last_mouse_offset_.y)];
}
}
void NativeWindowMac::HandleKeyboardEvent( void NativeWindowMac::HandleKeyboardEvent(
content::WebContents*, content::WebContents*,
const content::NativeWebKeyboardEvent& event) { const content::NativeWebKeyboardEvent& event) {
@ -800,6 +747,23 @@ void NativeWindowMac::HandleKeyboardEvent(
} }
} }
std::vector<gfx::Rect> NativeWindowMac::CalculateNonDraggableRegions(
const std::vector<DraggableRegion>& regions, int width, int height) {
std::vector<gfx::Rect> result;
if (regions.empty()) {
result.push_back(gfx::Rect(0, 0, width, height));
} else {
scoped_ptr<SkRegion> draggable(DraggableRegionsToSkRegion(regions));
scoped_ptr<SkRegion> non_draggable(new SkRegion);
non_draggable->op(0, 0, width, height, SkRegion::kUnion_Op);
non_draggable->op(*draggable, SkRegion::kDifference_Op);
for (SkRegion::Iterator it(*non_draggable); !it.done(); it.next()) {
result.push_back(gfx::SkIRectToRect(it.rect()));
}
}
return result;
}
gfx::Size NativeWindowMac::ContentSizeToWindowSize(const gfx::Size& size) { gfx::Size NativeWindowMac::ContentSizeToWindowSize(const gfx::Size& size) {
if (!has_frame()) if (!has_frame())
return size; return size;
@ -818,6 +782,13 @@ gfx::Size NativeWindowMac::WindowSizeToContentSize(const gfx::Size& size) {
return gfx::Size(content.size); return gfx::Size(content.size);
} }
void NativeWindowMac::UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions) {
NativeWindow::UpdateDraggableRegions(regions);
draggable_regions_ = regions;
UpdateDraggableRegionViews(regions);
}
void NativeWindowMac::InstallView() { void NativeWindowMac::InstallView() {
// Make sure the bottom corner is rounded: http://crbug.com/396264. // Make sure the bottom corner is rounded: http://crbug.com/396264.
[[window_ contentView] setWantsLayer:YES]; [[window_ contentView] setWantsLayer:YES];
@ -840,8 +811,6 @@ void NativeWindowMac::InstallView() {
[view setFrame:[content_view_ bounds]]; [view setFrame:[content_view_ bounds]];
[content_view_ addSubview:view]; [content_view_ addSubview:view];
InstallDraggableRegionView();
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES]; [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES];
@ -858,16 +827,57 @@ void NativeWindowMac::UninstallView() {
[view removeFromSuperview]; [view removeFromSuperview];
} }
void NativeWindowMac::InstallDraggableRegionView() { void NativeWindowMac::UpdateDraggableRegionViews(
const std::vector<DraggableRegion>& regions) {
if (has_frame() && !force_using_draggable_region())
return;
// All ControlRegionViews should be added as children of the WebContentsView,
// because WebContentsView will be removed and re-added when entering and
// leaving fullscreen mode.
NSView* webView = web_contents()->GetNativeView(); NSView* webView = web_contents()->GetNativeView();
NSInteger webViewWidth = NSWidth([webView bounds]);
NSInteger webViewHeight = NSHeight([webView bounds]);
[webView setMouseDownCanMoveWindow:YES];
// Remove all ControlRegionViews that are added last time.
// Note that [webView subviews] returns the view's mutable internal array and
// it should be copied to avoid mutating the original array while enumerating
// it.
base::scoped_nsobject<NSArray> subviews([[webView subviews] copy]);
for (NSView* subview in subviews.get())
if ([subview isKindOfClass:[ControlRegionView class]])
[subview removeFromSuperview];
// Draggable regions is implemented by having the whole web view draggable
// (mouseDownCanMoveWindow) and overlaying regions that are not draggable.
std::vector<gfx::Rect> system_drag_exclude_areas =
CalculateNonDraggableRegions(regions, webViewWidth, webViewHeight);
// Create and add a ControlRegionView for each region that needs to be
// excluded from the dragging.
for (std::vector<gfx::Rect>::const_iterator iter =
system_drag_exclude_areas.begin();
iter != system_drag_exclude_areas.end();
++iter) {
base::scoped_nsobject<NSView> controlRegion( base::scoped_nsobject<NSView> controlRegion(
[[ControlRegionView alloc] initWithShellWindow:this]); [[ControlRegionView alloc] initWithFrame:NSZeroRect]);
[controlRegion setFrame:NSMakeRect(0, 0, [controlRegion setFrame:NSMakeRect(iter->x(),
NSWidth([webView bounds]), webViewHeight - iter->bottom(),
NSHeight([webView bounds]))]; iter->width(),
iter->height())];
[webView addSubview:controlRegion]; [webView addSubview:controlRegion];
} }
// AppKit will not update its cache of mouseDownCanMoveWindow unless something
// changes. Previously we tried adding an NSView and removing it, but for some
// reason it required reposting the mouse-down event, and didn't always work.
// Calling the below seems to be an effective solution.
[window_ setMovableByWindowBackground:NO];
[window_ setMovableByWindowBackground:YES];
}
// static // static
NativeWindow* NativeWindow::Create( NativeWindow* NativeWindow::Create(
brightray::InspectableWebContents* inspectable_web_contents, brightray::InspectableWebContents* inspectable_web_contents,