summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/VibrancyManager.mm
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /widget/cocoa/VibrancyManager.mm
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/cocoa/VibrancyManager.mm')
-rw-r--r--widget/cocoa/VibrancyManager.mm271
1 files changed, 271 insertions, 0 deletions
diff --git a/widget/cocoa/VibrancyManager.mm b/widget/cocoa/VibrancyManager.mm
new file mode 100644
index 000000000..b6176de2b
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.mm
@@ -0,0 +1,271 @@
+/* -*- 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];
+}
+
+@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 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)
+{
+ // 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}}");
+ 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
+
+NSView*
+VibrancyManager::CreateEffectView(VibrancyType aType)
+{
+ static Class EffectViewClassWithoutForegroundVibrancy = CreateEffectViewClass(NO);
+ static Class EffectViewClassWithForegroundVibrancy = CreateEffectViewClass(YES);
+
+ Class EffectViewClass = HasVibrantForeground(aType)
+ ? EffectViewClassWithForegroundVibrancy : EffectViewClassWithoutForegroundVibrancy;
+ 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;
+}