Bug 1051522 - Create NSVisualEffectViews for vibrant window regions. r=smichaud

This commit is contained in:
Markus Stange 2014-08-28 02:15:33 +02:00
parent c4de2026fb
commit 8a49452f67
7 changed files with 349 additions and 4 deletions

View File

@ -0,0 +1,103 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef VibrancyManager_h
#define VibrancyManager_h
#include "mozilla/Assertions.h"
#include "mozilla/TypedEnum.h"
#include "nsClassHashtable.h"
#include "nsRegion.h"
#include "nsTArray.h"
#import <Foundation/NSGeometry.h>
@class NSView;
class nsChildView;
class nsIntRegion;
namespace mozilla {
MOZ_BEGIN_ENUM_CLASS(VibrancyType)
LIGHT,
DARK
MOZ_END_ENUM_CLASS(VibrancyType)
/**
* VibrancyManager takes care of updating the vibrant regions of a window.
* Vibrancy is a visual look that was introduced on OS X starting with 10.10.
* An app declares vibrant window regions to the window server, and the window
* server will display a blurred rendering of the screen contents from behind
* the window in these areas, behind the actual window contents. Consequently,
* the effect is only visible in areas where the window contents are not
* completely opaque. Usually this is achieved by clearing the background of
* the window prior to drawing in the vibrant areas. This is possible even if
* the window is declared as opaque.
*/
class VibrancyManager {
public:
/**
* Create a new VibrancyManager instance and provide it with an NSView
* to attach NSVisualEffectViews to.
*
* @param aCoordinateConverter The nsChildView to use for converting
* nsIntRect device pixel coordinates into Cocoa NSRect coordinates. Must
* outlive this VibrancyManager instance.
* @param aContainerView The view that's going to be the superview of the
* NSVisualEffectViews which will be created for vibrant regions.
*/
VibrancyManager(const nsChildView& aCoordinateConverter,
NSView* aContainerView)
: mCoordinateConverter(aCoordinateConverter)
, mContainerView(aContainerView)
{
MOZ_ASSERT(SystemSupportsVibrancy(),
"Don't instantiate this if !SystemSupportsVibrancy()");
}
/**
* Update the placement of the NSVisualEffectViews inside the container
* NSView so that they cover aRegion, and create new NSVisualEffectViews
* or remove existing ones as needed.
* @param aType The vibrancy type to use in the region.
* @param aRegion The vibrant area, in device pixels.
*/
void UpdateVibrantRegion(VibrancyType aType, const nsIntRegion& aRegion);
/**
* Clear the vibrant areas that we know about.
* The clearing happens in the current NSGraphicsContext. If you call this
* from within an -[NSView drawRect:] implementation, the currrent
* NSGraphicsContext is already correctly set to the window drawing context.
*/
void ClearVibrantAreas() const;
/**
* Check whether the operating system supports vibrancy at all.
* You may only create a VibrancyManager instance if this returns true.
* @return Whether VibrancyManager can be used on this OS.
*/
static bool SystemSupportsVibrancy();
// The following are only public because otherwise ClearVibrantRegionFunc
// can't see them.
struct VibrantRegion {
nsIntRegion region;
nsTArray<NSView*> effectViews;
};
void ClearVibrantRegion(const VibrantRegion& aVibrantRegion) const;
protected:
NSView* CreateEffectView(VibrancyType aType, NSRect aRect);
const nsChildView& mCoordinateConverter;
NSView* mContainerView;
nsClassHashtable<nsUint32HashKey, VibrantRegion> mVibrantRegions;
};
}
#endif // VibrancyManager_h

View File

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VibrancyManager.h"
#include "nsChildView.h"
#import <objc/message.h>
using namespace mozilla;
void
VibrancyManager::UpdateVibrantRegion(VibrancyType aType, const nsIntRegion& aRegion)
{
auto& vr = *mVibrantRegions.LookupOrAdd(uint32_t(aType));
if (vr.region == aRegion) {
return;
}
// We need to construct the required region using as many EffectViews
// as necessary. We try to update the geometry of existing views if
// possible, or create new ones or remove old ones if the number of
// rects in the region has changed.
nsTArray<NSView*> viewsToRecycle;
vr.effectViews.SwapElements(viewsToRecycle);
// vr.effectViews is now empty.
nsIntRegionRectIterator iter(aRegion);
const nsIntRect* iterRect = nullptr;
for (size_t i = 0; (iterRect = iter.Next()) || i < viewsToRecycle.Length(); ++i) {
if (iterRect) {
NSView* view = nil;
NSRect rect = mCoordinateConverter.DevPixelsToCocoaPoints(*iterRect);
if (i < viewsToRecycle.Length()) {
view = viewsToRecycle[i];
[view setFrame:rect];
} else {
view = CreateEffectView(aType, rect);
[mContainerView addSubview:view];
// Now that the view is in the view hierarchy, it'll be kept alive by
// its superview, so we can drop our reference.
[view release];
}
vr.effectViews.AppendElement(view);
} else {
// Our new region is made of less rects than the old region, so we can
// remove this view. We only have a weak reference to it, so removing it
// from the view hierarchy will release it.
[viewsToRecycle[i] removeFromSuperview];
}
}
vr.region = aRegion;
}
static PLDHashOperator
ClearVibrantRegionFunc(const uint32_t& aVibrancyType,
VibrancyManager::VibrantRegion* aVibrantRegion,
void* aVM)
{
static_cast<VibrancyManager*>(aVM)->ClearVibrantRegion(*aVibrantRegion);
return PL_DHASH_NEXT;
}
void
VibrancyManager::ClearVibrantAreas() const
{
mVibrantRegions.EnumerateRead(ClearVibrantRegionFunc,
const_cast<VibrancyManager*>(this));
}
void
VibrancyManager::ClearVibrantRegion(const VibrantRegion& aVibrantRegion) const
{
[[NSColor clearColor] set];
nsIntRegionRectIterator iter(aVibrantRegion.region);
while (const nsIntRect* rect = iter.Next()) {
NSRectFill(mCoordinateConverter.DevPixelsToCocoaPoints(*rect));
}
}
static void
DrawRectNothing(id self, SEL _cmd, NSRect aRect)
{
// The super implementation would clear the background.
// That's fine for views that are placed below their content, but our
// setup is different: Our drawn content is drawn to mContainerView, which
// sits below this EffectView. So we must not clear the background here,
// because we'd erase that drawn content.
// Of course the regular content drawing still needs to clear the background
// behind vibrant areas. This is taken care of by having nsNativeThemeCocoa
// return true from NeedToClearBackgroundBehindWidget for vibrant widgets.
}
static NSView*
HitTestNil(id self, SEL _cmd, NSPoint aPoint)
{
// This view must be transparent to mouse events.
return nil;
}
static Class
CreateEffectViewClass()
{
// Create a class called EffectView that inherits from NSVisualEffectView
// and overrides the methods -[NSVisualEffectView drawRect:] and
// -[NSView hitTest:].
Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, "EffectView", 0);
class_addMethod(EffectViewClass, @selector(drawRect:), (IMP)DrawRectNothing,
"v@:{CGRect={CGPoint=dd}{CGSize=dd}}");
class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
"@@:{CGPoint=dd}");
return EffectViewClass;
}
static id
AppearanceForVibrancyType(VibrancyType aType)
{
Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
switch (aType) {
case VibrancyType::LIGHT:
return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
withObject:@"NSAppearanceNameVibrantLight"];
case VibrancyType::DARK:
return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
withObject:@"NSAppearanceNameVibrantDark"];
}
}
NSView*
VibrancyManager::CreateEffectView(VibrancyType aType, NSRect aRect)
{
static Class EffectViewClass = CreateEffectViewClass();
NSView* effectView = [[EffectViewClass alloc] initWithFrame:aRect];
[effectView performSelector:@selector(setAppearance:)
withObject:AppearanceForVibrancyType(aType)];
return effectView;
}
static bool
ComputeSystemSupportsVibrancy()
{
return NSClassFromString(@"NSAppearance") &&
NSClassFromString(@"NSVisualEffectView");
}
/* static */ bool
VibrancyManager::SystemSupportsVibrancy()
{
static bool supportsVibrancy = ComputeSystemSupportsVibrancy();
return supportsVibrancy;
}

View File

@ -53,6 +53,7 @@ UNIFIED_SOURCES += [
'nsWidgetFactory.mm',
'nsWindowMap.mm',
'OSXNotificationCenter.mm',
'VibrancyManager.mm',
'WidgetTraceEvent.mm',
]

View File

@ -26,6 +26,7 @@
#include "mozilla/Mutex.h"
#include "nsRegion.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/UniquePtr.h"
#include "nsString.h"
#include "nsIDragService.h"
@ -93,6 +94,7 @@ class RectTextureImage;
}
namespace mozilla {
class VibrancyManager;
namespace layers {
class GLManager;
class APZCTreeManager;
@ -569,6 +571,7 @@ public:
}
void NotifyDirtyRegion(const nsIntRegion& aDirtyRegion);
void ClearVibrantAreas();
// unit conversion convenience functions
int32_t CocoaPointsToDevPixels(CGFloat aPts) const {
@ -625,6 +628,8 @@ protected:
void UpdateTitlebarCGContext();
nsIntRect RectContainingTitlebarControls();
void UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries);
mozilla::VibrancyManager& EnsureVibrancyManager();
nsIWidget* GetWidgetForListenerEvents();
@ -694,6 +699,8 @@ protected:
nsRefPtr<APZCTreeManager> mAPZCTreeManager;
mozilla::UniquePtr<mozilla::VibrancyManager> mVibrancyManager;
static uint32_t sLastInputEventCount;
void ReleaseTitlebarCGContext();

View File

@ -94,6 +94,7 @@
#include "mozilla/layers/APZCCallbackHelper.h"
#include "nsLayoutUtils.h"
#include "InputData.h"
#include "VibrancyManager.h"
using namespace mozilla;
using namespace mozilla::layers;
@ -2595,7 +2596,12 @@ FindFirstRectOfType(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
void
nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries)
{
if (![mView window] || ![[mView window] isKindOfClass:[ToolbarWindow class]])
if (![mView window])
return;
UpdateVibrancy(aThemeGeometries);
if (![[mView window] isKindOfClass:[ToolbarWindow class]])
return;
// Update unified toolbar height.
@ -2618,6 +2624,58 @@ nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometri
[win placeFullScreenButton:[mView convertRect:DevPixelsToCocoaPoints(fullScreenButtonRect) toView:nil]];
}
static nsIntRegion
GatherThemeGeometryRegion(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
uint8_t aWidgetType)
{
nsIntRegion region;
for (size_t i = 0; i < aThemeGeometries.Length(); ++i) {
const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
if (g.mWidgetType == aWidgetType) {
region.OrWith(g.mRect);
}
}
return region;
}
void
nsChildView::UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries)
{
if (!VibrancyManager::SystemSupportsVibrancy()) {
return;
}
nsIntRegion vibrantLightRegion =
GatherThemeGeometryRegion(aThemeGeometries, NS_THEME_MAC_VIBRANCY_LIGHT);
nsIntRegion vibrantDarkRegion =
GatherThemeGeometryRegion(aThemeGeometries, NS_THEME_MAC_VIBRANCY_DARK);
// Make light win over dark in disputed areas.
vibrantDarkRegion.SubOut(vibrantLightRegion);
auto& vm = EnsureVibrancyManager();
vm.UpdateVibrantRegion(VibrancyType::LIGHT, vibrantLightRegion);
vm.UpdateVibrantRegion(VibrancyType::DARK, vibrantDarkRegion);
}
void
nsChildView::ClearVibrantAreas()
{
if (VibrancyManager::SystemSupportsVibrancy()) {
EnsureVibrancyManager().ClearVibrantAreas();
}
}
mozilla::VibrancyManager&
nsChildView::EnsureVibrancyManager()
{
MOZ_ASSERT(mView, "Only call this once we have a view!");
if (!mVibrancyManager) {
mVibrancyManager = MakeUnique<VibrancyManager>(*this, mView);
}
return *mVibrancyManager;
}
TemporaryRef<gfx::DrawTarget>
nsChildView::StartRemoteDrawing()
{
@ -3645,9 +3703,10 @@ NSEvent* gLastDragMouseDownEvent = nil;
// Since this view is usually declared as opaque, the window's pixel
// buffer may now contain garbage which we need to prevent from reaching
// the screen. The only place where garbage can show is in the window
// corners - the rest of the window is covered by opaque content in our
// OpenGL surface.
// So we need to clear the pixel buffer contents in the corners.
// corners and the vibrant regions of the window - the rest of the window
// is covered by opaque content in our OpenGL surface.
// So we need to clear the pixel buffer contents in these areas.
mGeckoChild->ClearVibrantAreas();
[self clearCorners];
// Do GL composition and return.

View File

@ -62,6 +62,7 @@ public:
bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) MOZ_OVERRIDE;
bool ThemeNeedsComboboxDropmarker();
virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) MOZ_OVERRIDE;
virtual bool NeedToClearBackgroundBehindWidget(uint8_t aWidgetType) MOZ_OVERRIDE;
virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType);
void DrawProgress(CGContextRef context, const HIRect& inBoxRect,

View File

@ -28,6 +28,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLMeterElement.h"
#include "nsLookAndFeel.h"
#include "VibrancyManager.h"
#include "gfxContext.h"
#include "gfxQuartzSurface.h"
@ -3530,6 +3531,10 @@ nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* a
}
case NS_THEME_FOCUS_OUTLINE:
return true;
case NS_THEME_MAC_VIBRANCY_LIGHT:
case NS_THEME_MAC_VIBRANCY_DARK:
return VibrancyManager::SystemSupportsVibrancy();
}
return false;
@ -3604,6 +3609,18 @@ nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
}
}
bool
nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(uint8_t aWidgetType)
{
switch (aWidgetType) {
case NS_THEME_MAC_VIBRANCY_LIGHT:
case NS_THEME_MAC_VIBRANCY_DARK:
return true;
default:
return false;
}
}
nsITheme::Transparency
nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
{