Merge pull request #3163 from atom/fix-osx-app-region

osx: Implement draggable region with mouseDownCanMoveWindow
This commit is contained in:
Cheng Zhao 2015-10-21 09:53:02 +08:00
commit 567d8e7434
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,14 +827,55 @@ 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();
base::scoped_nsobject<NSView> controlRegion( NSInteger webViewWidth = NSWidth([webView bounds]);
[[ControlRegionView alloc] initWithShellWindow:this]); NSInteger webViewHeight = NSHeight([webView bounds]);
[controlRegion setFrame:NSMakeRect(0, 0,
NSWidth([webView bounds]), [webView setMouseDownCanMoveWindow:YES];
NSHeight([webView bounds]))];
[webView addSubview:controlRegion]; // 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(
[[ControlRegionView alloc] initWithFrame:NSZeroRect]);
[controlRegion setFrame:NSMakeRect(iter->x(),
webViewHeight - iter->bottom(),
iter->width(),
iter->height())];
[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