summaryrefslogtreecommitdiff
path: root/widget/cocoa/VibrancyManager.mm
diff options
context:
space:
mode:
Diffstat (limited to 'widget/cocoa/VibrancyManager.mm')
-rw-r--r--widget/cocoa/VibrancyManager.mm244
1 files changed, 244 insertions, 0 deletions
diff --git a/widget/cocoa/VibrancyManager.mm b/widget/cocoa/VibrancyManager.mm
new file mode 100644
index 0000000000..d02338eb6b
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.mm
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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);
+ });
+}
+
+@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 the view
+ // draws in its drawRect implementation.
+ return AdjustedColor([view _currentFillColor], aType);
+ }
+ return [NSColor whiteColor];
+}
+
+@interface NSView(FontSmoothingBackgroundColor)
+- (NSColor*)fontSmoothingBackgroundColor;
+@end
+
+NSColor*
+VibrancyManager::VibrancyFontSmoothingBackgroundColorForType(VibrancyType aType)
+{
+ NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
+
+ if (view && [view respondsToSelector:@selector(fontSmoothingBackgroundColor)]) {
+ return [view fontSmoothingBackgroundColor];
+ }
+ return [NSColor clearColor];
+}
+
+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 that inherits from NSVisualEffectView and overrides the
+ // methods -[NSView hitTest:] and -[NSVisualEffectView allowsVibrancy].
+ Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
+ const char* className = aForegroundVibrancy
+ ? "EffectViewWithForegroundVibrancy" : "EffectViewWithoutForegroundVibrancy";
+ Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, className, 0);
+ if (!aIsContainer) {
+ class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
+ "@@:{CGPoint=dd}");
+ }
+ if (aForegroundVibrancy) {
+ // 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
+
+NSView*
+VibrancyManager::CreateEffectView(VibrancyType aType, BOOL aIsContainer)
+{
+ static Class EffectViewWithoutForegroundVibrancy = CreateEffectViewClass(NO, NO);
+ static Class EffectViewWithForegroundVibrancy = CreateEffectViewClass(YES, NO);
+ static Class EffectViewContainer = CreateEffectViewClass(NO, YES);
+
+ // Pick the right NSVisualEffectView subclass for the desired vibrancy mode.
+ // For "container" views, never use foreground vibrancy, because returning
+ // YES from allowsVibrancy forces on foreground vibrancy for all descendant
+ // views which can have unintended effects.
+ Class EffectViewClass = aIsContainer
+ ? EffectViewContainer
+ : (HasVibrantForeground(aType) ? EffectViewWithForegroundVibrancy
+ : EffectViewWithoutForegroundVibrancy);
+ 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;
+}