mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
a0e9560488
The idea is the following: Behind-window vibrancy is mostly rendered by the window server. For a given vibrant region of a window, the window server renders a vibrancy "backdrop", which is a blurred version of everything that's behind that region, modified with a color tint and blended in some way. Then it puts our actual window contents on top of that background. The backdrop's shape is usually a rectangle. If we don't want it to be a rectangle, we need to tell the window server about the shape that we want it to be. We can't just "draw" a different shape in our own rendering, because our own rendering is merely placed on top of the backdrop - but here we want to modify the shape of the backdrop itself. NSVisualEffectView lets us set a mask image on the view. If this view is the content view of a window, then the view will automatically communicate the mask image to the window server. Traditionally, our popup windows have had a ChildView as their content view. If we now want an NSVisualEffectView to be the content view of the window, then we need to nest the ChildView inside that NSVisualEffectView. But this NSVisualEffectView is only needed when the window is vibrant and the vibrancy backdrop needs to have a certain shape. This is the case for our menus which need to have rounded corners. If the window transitions to being non-vibrant, or not needing a special shape, then we can go back to the way our window's NSView hierarchy has worked traditionally. So we need to reparent NSViews during those transitions. MozReview-Commit-ID: Bo2VzjhhR0A --HG-- extra : rebase_source : 9eb463cc68c16c3b9281b57455330969c5e2642c
260 lines
8.5 KiB
Plaintext
260 lines
8.5 KiB
Plaintext
/* -*- 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 LayoutDeviceIntRegion& aRegion)
|
|
{
|
|
if (aRegion.IsEmpty()) {
|
|
mVibrantRegions.Remove(uint32_t(aType));
|
|
return;
|
|
}
|
|
auto& vr = *mVibrantRegions.LookupOrAdd(uint32_t(aType));
|
|
vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() {
|
|
return this->CreateEffectView(aType);
|
|
});
|
|
}
|
|
|
|
void
|
|
VibrancyManager::ClearVibrantAreas() const
|
|
{
|
|
for (auto iter = mVibrantRegions.ConstIter(); !iter.Done(); iter.Next()) {
|
|
ClearVibrantRegion(iter.UserData()->Region());
|
|
}
|
|
}
|
|
|
|
void
|
|
VibrancyManager::ClearVibrantRegion(const LayoutDeviceIntRegion& aVibrantRegion) const
|
|
{
|
|
[[NSColor clearColor] set];
|
|
|
|
for (auto iter = aVibrantRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
NSRectFill(mCoordinateConverter.DevPixelsToCocoaPoints(iter.Get()));
|
|
}
|
|
}
|
|
|
|
@interface NSView(CurrentFillColor)
|
|
- (NSColor*)_currentFillColor;
|
|
@end
|
|
|
|
static NSColor*
|
|
AdjustedColor(NSColor* aFillColor, VibrancyType aType)
|
|
{
|
|
if (aType == VibrancyType::MENU && [aFillColor alphaComponent] == 1.0) {
|
|
// The opaque fill color that's used for the menu background when "Reduce
|
|
// vibrancy" is checked in the system accessibility prefs is too dark.
|
|
// This is probably because we're not using the right material for menus,
|
|
// see VibrancyManager::CreateEffectView.
|
|
return [NSColor colorWithDeviceWhite:0.96 alpha:1.0];
|
|
}
|
|
return aFillColor;
|
|
}
|
|
|
|
NSColor*
|
|
VibrancyManager::VibrancyFillColorForType(VibrancyType aType)
|
|
{
|
|
NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
|
|
|
|
if (view && [view respondsToSelector:@selector(_currentFillColor)]) {
|
|
// -[NSVisualEffectView _currentFillColor] is the color that our view
|
|
// would draw during its drawRect implementation, if we hadn't
|
|
// disabled that.
|
|
return AdjustedColor([view _currentFillColor], aType);
|
|
}
|
|
return [NSColor whiteColor];
|
|
}
|
|
|
|
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 BOOL
|
|
AllowsVibrancyYes(id self, SEL _cmd)
|
|
{
|
|
// Means that the foreground is blended using a vibrant blend mode.
|
|
return YES;
|
|
}
|
|
|
|
static Class
|
|
CreateEffectViewClass(BOOL aForegroundVibrancy, BOOL aIsContainer)
|
|
{
|
|
// Create a class called EffectView that inherits from NSVisualEffectView
|
|
// and overrides the methods -[NSVisualEffectView drawRect:] and
|
|
// -[NSView hitTest:].
|
|
Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
|
|
const char* className = aForegroundVibrancy
|
|
? "EffectViewWithForegroundVibrancy" : "EffectViewWithoutForegroundVibrancy";
|
|
Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, className, 0);
|
|
class_addMethod(EffectViewClass, @selector(drawRect:), (IMP)DrawRectNothing,
|
|
"v@:{CGRect={CGPoint=dd}{CGSize=dd}}");
|
|
if (!aIsContainer) {
|
|
class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
|
|
"@@:{CGPoint=dd}");
|
|
}
|
|
if (aForegroundVibrancy) {
|
|
// Also override the -[NSView allowsVibrancy] method to return YES.
|
|
class_addMethod(EffectViewClass, @selector(allowsVibrancy), (IMP)AllowsVibrancyYes, "I@:");
|
|
}
|
|
return EffectViewClass;
|
|
}
|
|
|
|
static id
|
|
AppearanceForVibrancyType(VibrancyType aType)
|
|
{
|
|
Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
|
|
switch (aType) {
|
|
case VibrancyType::LIGHT:
|
|
case VibrancyType::TOOLTIP:
|
|
case VibrancyType::MENU:
|
|
case VibrancyType::HIGHLIGHTED_MENUITEM:
|
|
case VibrancyType::SHEET:
|
|
case VibrancyType::SOURCE_LIST:
|
|
case VibrancyType::SOURCE_LIST_SELECTION:
|
|
case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
|
|
return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
|
|
withObject:@"NSAppearanceNameVibrantLight"];
|
|
case VibrancyType::DARK:
|
|
return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
|
|
withObject:@"NSAppearanceNameVibrantDark"];
|
|
}
|
|
}
|
|
|
|
#if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
|
|
enum {
|
|
NSVisualEffectStateFollowsWindowActiveState,
|
|
NSVisualEffectStateActive,
|
|
NSVisualEffectStateInactive
|
|
};
|
|
|
|
enum {
|
|
NSVisualEffectMaterialTitlebar = 3
|
|
};
|
|
#endif
|
|
|
|
#if !defined(MAC_OS_X_VERSION_10_11) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
|
|
enum {
|
|
NSVisualEffectMaterialMenu = 5,
|
|
NSVisualEffectMaterialSidebar = 7
|
|
};
|
|
#endif
|
|
|
|
static NSUInteger
|
|
VisualEffectStateForVibrancyType(VibrancyType aType)
|
|
{
|
|
switch (aType) {
|
|
case VibrancyType::TOOLTIP:
|
|
case VibrancyType::MENU:
|
|
case VibrancyType::HIGHLIGHTED_MENUITEM:
|
|
case VibrancyType::SHEET:
|
|
// Tooltip and menu windows are never "key" and sheets always looks
|
|
// active, so we need to tell the vibrancy effect to look active
|
|
// regardless of window state.
|
|
return NSVisualEffectStateActive;
|
|
default:
|
|
return NSVisualEffectStateFollowsWindowActiveState;
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
HasVibrantForeground(VibrancyType aType)
|
|
{
|
|
switch (aType) {
|
|
case VibrancyType::MENU:
|
|
return YES;
|
|
default:
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
|
|
enum {
|
|
NSVisualEffectMaterialSelection = 4
|
|
};
|
|
#endif
|
|
|
|
@interface NSView(NSVisualEffectViewMethods)
|
|
- (void)setState:(NSUInteger)state;
|
|
- (void)setMaterial:(NSUInteger)material;
|
|
- (void)setEmphasized:(BOOL)emphasized;
|
|
@end
|
|
|
|
/* static */ NSView*
|
|
VibrancyManager::CreateEffectView(VibrancyType aType, BOOL aIsContainer)
|
|
{
|
|
static Class EffectViewClasses[2][2] = {
|
|
{ CreateEffectViewClass(NO, NO), CreateEffectViewClass(NO, YES) },
|
|
{ CreateEffectViewClass(YES, NO), CreateEffectViewClass(YES, YES) }
|
|
};
|
|
|
|
Class EffectViewClass = EffectViewClasses[HasVibrantForeground(aType)][aIsContainer];
|
|
NSView* effectView = [[EffectViewClass alloc] initWithFrame:NSZeroRect];
|
|
[effectView performSelector:@selector(setAppearance:)
|
|
withObject:AppearanceForVibrancyType(aType)];
|
|
[effectView setState:VisualEffectStateForVibrancyType(aType)];
|
|
|
|
BOOL canUseElCapitanMaterials = nsCocoaFeatures::OnElCapitanOrLater();
|
|
if (aType == VibrancyType::MENU) {
|
|
// Before 10.11 there is no material that perfectly matches the menu
|
|
// look. Of all available material types, NSVisualEffectMaterialTitlebar
|
|
// is the one that comes closest.
|
|
[effectView setMaterial:canUseElCapitanMaterials ? NSVisualEffectMaterialMenu
|
|
: NSVisualEffectMaterialTitlebar];
|
|
} else if (aType == VibrancyType::SOURCE_LIST && canUseElCapitanMaterials) {
|
|
[effectView setMaterial:NSVisualEffectMaterialSidebar];
|
|
} else if (aType == VibrancyType::HIGHLIGHTED_MENUITEM ||
|
|
aType == VibrancyType::SOURCE_LIST_SELECTION ||
|
|
aType == VibrancyType::ACTIVE_SOURCE_LIST_SELECTION) {
|
|
[effectView setMaterial:NSVisualEffectMaterialSelection];
|
|
if ([effectView respondsToSelector:@selector(setEmphasized:)] &&
|
|
aType != VibrancyType::SOURCE_LIST_SELECTION) {
|
|
[effectView setEmphasized:YES];
|
|
}
|
|
}
|
|
|
|
return effectView;
|
|
}
|
|
|
|
static bool
|
|
ComputeSystemSupportsVibrancy()
|
|
{
|
|
#ifdef __x86_64__
|
|
return NSClassFromString(@"NSAppearance") &&
|
|
NSClassFromString(@"NSVisualEffectView");
|
|
#else
|
|
// objc_allocateClassPair doesn't work in 32 bit mode, so turn off vibrancy.
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/* static */ bool
|
|
VibrancyManager::SystemSupportsVibrancy()
|
|
{
|
|
static bool supportsVibrancy = ComputeSystemSupportsVibrancy();
|
|
return supportsVibrancy;
|
|
}
|