1/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7#include "VibrancyManager.h"
8
9#import <objc/message.h>
10
11#include "nsChildView.h"
12#include "nsCocoaFeatures.h"
13#include "SDKDeclarations.h"
14
15using namespace mozilla;
16
17@interface MOZVibrantView : NSVisualEffectView {
18  VibrancyType mType;
19}
20- (instancetype)initWithFrame:(NSRect)aRect vibrancyType:(VibrancyType)aVibrancyType;
21@end
22
23@interface MOZVibrantLeafView : MOZVibrantView
24@end
25
26static NSAppearance* AppearanceForVibrancyType(VibrancyType aType) {
27  if (@available(macOS 10.14, *)) {
28    switch (aType) {
29      case VibrancyType::TITLEBAR_LIGHT:
30        // This must always be light (regular aqua), regardless of window appearance.
31        return [NSAppearance appearanceNamed:NSAppearanceNameAqua];
32      case VibrancyType::TITLEBAR_DARK:
33        // This must always be dark (dark aqua), regardless of window appearance.
34        return [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
35      case VibrancyType::TOOLTIP:
36      case VibrancyType::MENU:
37      case VibrancyType::HIGHLIGHTED_MENUITEM:
38      case VibrancyType::SOURCE_LIST:
39      case VibrancyType::SOURCE_LIST_SELECTION:
40      case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
41        // Inherit the appearance from the window. If the window is using Dark Mode, the vibrancy
42        // will automatically be dark, too. This is available starting with macOS 10.14.
43        return nil;
44    }
45  }
46
47  // For 10.13 and below, a vibrant appearance name must be used. There is no system dark mode and
48  // no automatic adaptation to the window; all windows are light.
49  switch (aType) {
50    case VibrancyType::TITLEBAR_LIGHT:
51    case VibrancyType::TOOLTIP:
52    case VibrancyType::MENU:
53    case VibrancyType::HIGHLIGHTED_MENUITEM:
54    case VibrancyType::SOURCE_LIST:
55    case VibrancyType::SOURCE_LIST_SELECTION:
56    case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
57      return [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight];
58    case VibrancyType::TITLEBAR_DARK:
59      return [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
60  }
61}
62
63static NSVisualEffectState VisualEffectStateForVibrancyType(VibrancyType aType) {
64  switch (aType) {
65    case VibrancyType::TOOLTIP:
66    case VibrancyType::MENU:
67    case VibrancyType::HIGHLIGHTED_MENUITEM:
68      // Tooltip and menu windows are never "key", so we need to tell the vibrancy effect to look
69      // active regardless of window state.
70      return NSVisualEffectStateActive;
71    default:
72      return NSVisualEffectStateFollowsWindowActiveState;
73  }
74}
75
76static NSVisualEffectMaterial VisualEffectMaterialForVibrancyType(VibrancyType aType,
77                                                                  BOOL* aOutIsEmphasized) {
78  switch (aType) {
79    case VibrancyType::TITLEBAR_LIGHT:
80    case VibrancyType::TITLEBAR_DARK:
81      return NSVisualEffectMaterialTitlebar;
82    case VibrancyType::TOOLTIP:
83      if (@available(macOS 10.14, *)) {
84        return (NSVisualEffectMaterial)NSVisualEffectMaterialToolTip;
85      } else {
86        return NSVisualEffectMaterialMenu;
87      }
88    case VibrancyType::MENU:
89      return NSVisualEffectMaterialMenu;
90    case VibrancyType::SOURCE_LIST:
91      return NSVisualEffectMaterialSidebar;
92    case VibrancyType::SOURCE_LIST_SELECTION:
93      return NSVisualEffectMaterialSelection;
94    case VibrancyType::HIGHLIGHTED_MENUITEM:
95    case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
96      *aOutIsEmphasized = YES;
97      return NSVisualEffectMaterialSelection;
98  }
99}
100
101static BOOL HasVibrantForeground(VibrancyType aType) {
102  switch (aType) {
103    case VibrancyType::MENU:
104      return YES;
105    default:
106      return NO;
107  }
108}
109
110@implementation MOZVibrantView
111
112- (instancetype)initWithFrame:(NSRect)aRect vibrancyType:(VibrancyType)aType {
113  self = [super initWithFrame:aRect];
114  mType = aType;
115
116  self.appearance = AppearanceForVibrancyType(mType);
117  self.state = VisualEffectStateForVibrancyType(mType);
118
119  BOOL isEmphasized = NO;
120  self.material = VisualEffectMaterialForVibrancyType(mType, &isEmphasized);
121  self.emphasized = isEmphasized;
122
123  return self;
124}
125
126// Don't override allowsVibrancy here, because this view may have subviews, and
127// returning YES from allowsVibrancy forces on foreground vibrancy for all
128// descendant views, which can have unintended effects.
129
130@end
131
132@implementation MOZVibrantLeafView
133
134- (NSView*)hitTest:(NSPoint)aPoint {
135  // This view must be transparent to mouse events.
136  return nil;
137}
138
139// MOZVibrantLeafView does not have subviews, so we can return YES here without
140// having unintended effects on other contents of the window.
141- (BOOL)allowsVibrancy {
142  return HasVibrantForeground(mType);
143}
144
145@end
146
147bool VibrancyManager::UpdateVibrantRegion(VibrancyType aType,
148                                          const LayoutDeviceIntRegion& aRegion) {
149  if (aRegion.IsEmpty()) {
150    return mVibrantRegions.Remove(uint32_t(aType));
151  }
152  auto& vr = *mVibrantRegions.GetOrInsertNew(uint32_t(aType));
153  return vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() {
154    return this->CreateEffectView(aType);
155  });
156}
157
158LayoutDeviceIntRegion VibrancyManager::GetUnionOfVibrantRegions() const {
159  LayoutDeviceIntRegion result;
160  for (const auto& region : mVibrantRegions.Values()) {
161    result.OrWith(region->Region());
162  }
163  return result;
164}
165
166/* static */ NSView* VibrancyManager::CreateEffectView(VibrancyType aType, BOOL aIsContainer) {
167  return aIsContainer ? [[MOZVibrantView alloc] initWithFrame:NSZeroRect vibrancyType:aType]
168                      : [[MOZVibrantLeafView alloc] initWithFrame:NSZeroRect vibrancyType:aType];
169}
170