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 {
// 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(
brightray::InspectableWebContents* inspectable_web_contents,
const mate::Dictionary& options)
@ -480,6 +460,20 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand(
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(
content::RenderViewHost* render_view_host) {
if (!transparent_)

View file

@ -241,10 +241,19 @@ class NativeWindow : public base::SupportsUserData,
NativeWindow(brightray::InspectableWebContents* inspectable_web_contents,
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.
virtual gfx::Size ContentSizeToWindowSize(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:
void RenderViewCreated(content::RenderViewHost* render_view_host) override;
void BeforeUnloadDialogCancelled() override;
@ -252,10 +261,6 @@ class NativeWindow : public base::SupportsUserData,
bool OnMessageReceived(const IPC::Message& message) override;
private:
// Called when the window needs to update its draggable region.
void UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions);
// Schedule a notification unresponsive event.
void ScheduleUnresponsiveEvent(int ms);

View file

@ -71,12 +71,10 @@ class NativeWindowMac : public NativeWindow {
void SetVisibleOnAllWorkspaces(bool visible) override;
bool IsVisibleOnAllWorkspaces() override;
// Returns true if |point| in local Cocoa coordinate system falls within
// the draggable region.
bool IsWithinDraggableRegion(NSPoint point) const;
// Called to handle a mouse event.
void HandleMouseEvent(NSEvent* event);
// Refresh the DraggableRegion views.
void UpdateDraggableRegionViews() {
UpdateDraggableRegionViews(draggable_regions_);
}
protected:
// NativeWindow:
@ -84,17 +82,24 @@ class NativeWindowMac : public NativeWindow {
content::WebContents*,
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:
// NativeWindow:
gfx::Size ContentSizeToWindowSize(const gfx::Size& size) override;
gfx::Size WindowSizeToContentSize(const gfx::Size& size) override;
void UpdateDraggableRegions(
const std::vector<DraggableRegion>& regions) override;
void InstallView();
void UninstallView();
// Install the drag view, which will cover the whole window and decides
// whehter we can drag.
void InstallDraggableRegionView();
void UpdateDraggableRegionViews(const std::vector<DraggableRegion>& regions);
base::scoped_nsobject<AtomNSWindow> window_;
base::scoped_nsobject<AtomNSWindowDelegate> window_delegate_;
@ -102,6 +107,8 @@ class NativeWindowMac : public NativeWindow {
// The view that will fill the whole frameless window.
base::scoped_nsobject<FullSizeContentView> content_view_;
std::vector<DraggableRegion> draggable_regions_;
bool is_kiosk_;
NSInteger attention_request_id_; // identifier from requestUserAttention
@ -109,10 +116,6 @@ class NativeWindowMac : public NativeWindow {
// The presentation options before entering kiosk mode.
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);
};

View file

@ -19,6 +19,7 @@
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "native_mate/dictionary.h"
#include "ui/gfx/skia_util.h"
namespace {
@ -146,6 +147,7 @@ bool ScopedDisableResize::disable_resize_ = false;
}
- (void)windowDidResize:(NSNotification*)notification {
shell_->UpdateDraggableRegionViews();
shell_->NotifyWindowResize();
}
@ -257,43 +259,23 @@ bool ScopedDisableResize::disable_resize_ = false;
@end
@interface ControlRegionView : NSView {
@private
atom::NativeWindowMac* shellWindow_; // Weak; owns self.
}
@interface ControlRegionView : NSView
@end
@implementation ControlRegionView
- (id)initWithShellWindow:(atom::NativeWindowMac*)shellWindow {
if ((self = [super init]))
shellWindow_ = shellWindow;
return self;
}
- (BOOL)mouseDownCanMoveWindow {
return NO;
}
- (NSView*)hitTest:(NSPoint)aPoint {
if (!shellWindow_->IsWithinDraggableRegion(aPoint)) {
return nil;
}
return self;
return nil;
}
- (void)mouseDown:(NSEvent*)event {
shellWindow_->HandleMouseEvent(event);
}
- (void)mouseDragged:(NSEvent*)event {
shellWindow_->HandleMouseEvent(event);
}
- (BOOL)acceptsFirstMouse:(NSEvent*)event {
return YES;
}
@end
@interface NSView (WebContentsView)
- (void)setMouseDownCanMoveWindow:(BOOL)can_move;
@end
@interface AtomProgressBar : NSProgressIndicator
@ -439,11 +421,6 @@ NativeWindowMac::NativeWindowMac(
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
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() {
@ -746,36 +723,6 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() {
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(
content::WebContents*,
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) {
if (!has_frame())
return size;
@ -818,6 +782,13 @@ gfx::Size NativeWindowMac::WindowSizeToContentSize(const gfx::Size& 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() {
// Make sure the bottom corner is rounded: http://crbug.com/396264.
[[window_ contentView] setWantsLayer:YES];
@ -840,8 +811,6 @@ void NativeWindowMac::InstallView() {
[view setFrame:[content_view_ bounds]];
[content_view_ addSubview:view];
InstallDraggableRegionView();
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES];
@ -858,14 +827,55 @@ void NativeWindowMac::UninstallView() {
[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();
base::scoped_nsobject<NSView> controlRegion(
[[ControlRegionView alloc] initWithShellWindow:this]);
[controlRegion setFrame:NSMakeRect(0, 0,
NSWidth([webView bounds]),
NSHeight([webView bounds]))];
[webView addSubview:controlRegion];
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(
[[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