// Copyright (c) 2017 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/native_browser_view_mac.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" #include "skia/ext/skia_utils_mac.h" #include "ui/gfx/geometry/rect.h" // Match view::Views behavior where the view sticks to the top-left origin. const NSAutoresizingMaskOptions kDefaultAutoResizingMask = NSViewMaxXMargin | NSViewMinYMargin; @interface DragRegionView : NSView @property (assign) NSPoint initialLocation; @end @interface NSWindow () - (void)performWindowDragWithEvent:(NSEvent *)event; @end @implementation DragRegionView - (BOOL)mouseDownCanMoveWindow { return NO; } - (NSView *)hitTest:(NSPoint)aPoint { // Pass-through events that don't hit one of the exclusion zones for (NSView *exlusion_zones in [self subviews]) { if ([exlusion_zones hitTest:aPoint]) return nil; } return self; } - (void)mouseDown:(NSEvent *)event { if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) { // According to Google, using performWindowDragWithEvent: // does not generate a NSWindowWillMoveNotification. Hence post one. [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillMoveNotification object:self]; [self.window performWindowDragWithEvent:event]; return; } if (self.window.styleMask & NSFullScreenWindowMask) { return; } self.initialLocation = [event locationInWindow]; } - (void)mouseDragged:(NSEvent *)theEvent { if ([self.window respondsToSelector:@selector(performWindowDragWithEvent)]) { return; } if (self.window.styleMask & NSFullScreenWindowMask) { return; } NSPoint currentLocation = [NSEvent mouseLocation]; NSPoint newOrigin; NSRect screenFrame = [[NSScreen mainScreen] frame]; NSSize screenSize = screenFrame.size; NSRect windowFrame = [self.window frame]; NSSize windowSize = windowFrame.size; newOrigin.x = currentLocation.x - self.initialLocation.x; newOrigin.y = currentLocation.y - self.initialLocation.y; BOOL inMenuBar = (newOrigin.y + windowSize.height) > (screenFrame.origin.y + screenSize.height); BOOL screenAboveMainScreen = false; if (inMenuBar) { for (NSScreen *screen in [NSScreen screens]) { NSRect currentScreenFrame = [screen frame]; BOOL isHigher = currentScreenFrame.origin.y > screenFrame.origin.y; // If there's another screen that is generally above the current screen, // we'll draw a new rectangle that is just above the current screen. If the // "higher" screen intersects with this rectangle, we'll allow drawing above // the menubar. if (isHigher) { NSRect aboveScreenRect = NSMakeRect( screenFrame.origin.x, screenFrame.origin.y + screenFrame.size.height - 10, screenFrame.size.width, 200 ); BOOL screenAboveIntersects = NSIntersectsRect(currentScreenFrame, aboveScreenRect); if (screenAboveIntersects) { screenAboveMainScreen = true; break; } } } } // Don't let window get dragged up under the menu bar if (inMenuBar && !screenAboveMainScreen) { newOrigin.y = screenFrame.origin.y + (screenFrame.size.height - windowFrame.size.height); } // Move the window to the new location [self.window setFrameOrigin:newOrigin]; } // Debugging tips: // Uncomment the following four lines to color DragRegionView bright red // #ifdef DEBUG_DRAG_REGIONS // - (void)drawRect:(NSRect)aRect // { // [[NSColor redColor] set]; // NSRectFill([self bounds]); // } // #endif @end @interface ExcludeDragRegionView : NSView @end @implementation ExcludeDragRegionView - (BOOL)mouseDownCanMoveWindow { return NO; } // Debugging tips: // Uncomment the following four lines to color ExcludeDragRegionView bright red // #ifdef DEBUG_DRAG_REGIONS // - (void)drawRect:(NSRect)aRect // { // [[NSColor greenColor] set]; // NSRectFill([self bounds]); // } // #endif @end namespace atom { NativeBrowserViewMac::NativeBrowserViewMac( brightray::InspectableWebContents* inspectable_web_contents) : NativeBrowserView(inspectable_web_contents) { auto* view = GetInspectableWebContentsView()->GetNativeView(); view.autoresizingMask = kDefaultAutoResizingMask; } NativeBrowserViewMac::~NativeBrowserViewMac() {} void NativeBrowserViewMac::SetAutoResizeFlags(uint8_t flags) { NSAutoresizingMaskOptions autoresizing_mask = kDefaultAutoResizingMask; if (flags & kAutoResizeWidth) { autoresizing_mask |= NSViewWidthSizable; } if (flags & kAutoResizeHeight) { autoresizing_mask |= NSViewHeightSizable; } auto* view = GetInspectableWebContentsView()->GetNativeView(); view.autoresizingMask = autoresizing_mask; } void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) { auto* view = GetInspectableWebContentsView()->GetNativeView(); auto* superview = view.superview; const auto superview_height = superview ? superview.frame.size.height : 0; view.frame = NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(), bounds.width(), bounds.height()); } void NativeBrowserViewMac::SetBackgroundColor(SkColor color) { auto* view = GetInspectableWebContentsView()->GetNativeView(); view.wantsLayer = YES; view.layer.backgroundColor = skia::CGColorCreateFromSkColor(color); } void NativeBrowserViewMac::UpdateDraggableRegions( const std::vector& system_drag_exclude_areas) { NSView* webView = GetInspectableWebContentsView()->GetNativeView(); NSInteger superViewHeight = NSHeight([webView.superview bounds]); NSInteger webViewHeight = NSHeight([webView bounds]); NSInteger webViewWidth = NSWidth([webView bounds]); NSInteger webViewX = NSMinX([webView frame]); NSInteger webViewY = 0; // Apple's NSViews have their coordinate system originate at the bottom left, // meaning that we need to be a bit smarter when it comes to calculating our // current top offset if (webViewHeight > superViewHeight) { webViewY = std::abs(webViewHeight - superViewHeight - (std::abs(NSMinY([webView frame])))); } else { webViewY = superViewHeight - NSMaxY([webView frame]); } // Remove all DraggableRegionViews 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 subviews([[webView subviews] copy]); for (NSView* subview in subviews.get()) if ([subview isKindOfClass:[DragRegionView class]]) [subview removeFromSuperview]; // Create one giant NSView that is draggable. base::scoped_nsobject dragRegion( [[DragRegionView alloc] initWithFrame:NSZeroRect]); [dragRegion setFrame:NSMakeRect(0, 0, webViewWidth, webViewHeight)]; // Then, on top of that, add "exclusion zones" for (auto iter = system_drag_exclude_areas.begin(); iter != system_drag_exclude_areas.end(); ++iter) { base::scoped_nsobject controlRegion( [[ExcludeDragRegionView alloc] initWithFrame:NSZeroRect]); [controlRegion setFrame:NSMakeRect(iter->x() - webViewX, webViewHeight - iter->bottom() + webViewY, iter->width(), iter->height())]; [dragRegion addSubview:controlRegion]; } // Add the DragRegion to the WebView [webView addSubview:dragRegion]; } // static NativeBrowserView* NativeBrowserView::Create( brightray::InspectableWebContents* inspectable_web_contents) { return new NativeBrowserViewMac(inspectable_web_contents); } } // namespace atom