diff --git a/widget/cocoa/VibrancyManager.h b/widget/cocoa/VibrancyManager.h index c70ec60a3b96..43adace794d8 100644 --- a/widget/cocoa/VibrancyManager.h +++ b/widget/cocoa/VibrancyManager.h @@ -7,24 +7,24 @@ #ifndef VibrancyManager_h #define VibrancyManager_h -#include "mozilla/Assertions.h" -#include "nsClassHashtable.h" -#include "nsRegion.h" -#include "nsTArray.h" -#include "ViewRegion.h" +#include "mozilla/EnumeratedArray.h" +#include "Units.h" -#import - -@class NSColor; @class NSView; class nsChildView; namespace mozilla { +class ViewRegion; + enum class VibrancyType { - TOOLTIP, - MENU, - TITLEBAR, + // Add new values here, or update MaxEnumValue below if you add them after. + Titlebar, +}; + +template <> +struct MaxContiguousEnumValue { + static constexpr auto value = VibrancyType::Titlebar; }; /** @@ -51,9 +51,9 @@ class VibrancyManager { * NSVisualEffectViews which will be created for vibrant regions. */ VibrancyManager(const nsChildView& aCoordinateConverter, - NSView* aContainerView) - : mCoordinateConverter(aCoordinateConverter), - mContainerView(aContainerView) {} + NSView* aContainerView); + + ~VibrancyManager(); /** * Update the placement of the NSVisualEffectViews inside the container @@ -66,26 +66,10 @@ class VibrancyManager { bool UpdateVibrantRegion(VibrancyType aType, const LayoutDeviceIntRegion& aRegion); - bool HasVibrantRegions() { return !mVibrantRegions.IsEmpty(); } - - LayoutDeviceIntRegion GetUnionOfVibrantRegions() const; - - /** - * Create an NSVisualEffectView for the specified vibrancy type. The return - * value is not autoreleased. We return an object of type NSView* because we - * compile with an SDK that does not contain a definition for - * NSVisualEffectView. - * @param aIsContainer Whether this NSView will have child views. This value - * affects hit testing: Container views will pass through - * hit testing requests to their children, and leaf views - * will be transparent to hit testing. - */ - static NSView* CreateEffectView(VibrancyType aType, BOOL aIsContainer = NO); - protected: const nsChildView& mCoordinateConverter; NSView* mContainerView; - nsClassHashtable mVibrantRegions; + EnumeratedArray> mVibrantRegions; }; } // namespace mozilla diff --git a/widget/cocoa/VibrancyManager.mm b/widget/cocoa/VibrancyManager.mm index 6062acb93131..a2bef29a19e2 100644 --- a/widget/cocoa/VibrancyManager.mm +++ b/widget/cocoa/VibrancyManager.mm @@ -5,6 +5,9 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "VibrancyManager.h" +#include "ViewRegion.h" +#include "nsRegion.h" +#include "ViewRegion.h" #import @@ -20,18 +23,10 @@ using namespace mozilla; vibrancyType:(VibrancyType)aVibrancyType; @end -@interface MOZVibrantLeafView : MOZVibrantView -@end - static NSVisualEffectState VisualEffectStateForVibrancyType( VibrancyType aType) { switch (aType) { - case VibrancyType::TOOLTIP: - case VibrancyType::MENU: - // Tooltip and menu windows are never "key", so we need to tell the - // vibrancy effect to look active regardless of window state. - return NSVisualEffectStateActive; - case VibrancyType::TITLEBAR: + case VibrancyType::Titlebar: break; } return NSVisualEffectStateFollowsWindowActiveState; @@ -40,11 +35,7 @@ static NSVisualEffectState VisualEffectStateForVibrancyType( static NSVisualEffectMaterial VisualEffectMaterialForVibrancyType( VibrancyType aType) { switch (aType) { - case VibrancyType::TOOLTIP: - return (NSVisualEffectMaterial)NSVisualEffectMaterialToolTip; - case VibrancyType::MENU: - return NSVisualEffectMaterialMenu; - case VibrancyType::TITLEBAR: + case VibrancyType::Titlebar: return NSVisualEffectMaterialTitlebar; } } @@ -52,10 +43,7 @@ static NSVisualEffectMaterial VisualEffectMaterialForVibrancyType( static NSVisualEffectBlendingMode VisualEffectBlendingModeForVibrancyType( VibrancyType aType) { switch (aType) { - case VibrancyType::TOOLTIP: - case VibrancyType::MENU: - return NSVisualEffectBlendingModeBehindWindow; - case VibrancyType::TITLEBAR: + case VibrancyType::Titlebar: return StaticPrefs::widget_macos_titlebar_blend_mode_behind_window() ? NSVisualEffectBlendingModeBehindWindow : NSVisualEffectBlendingModeWithinWindow; @@ -63,7 +51,6 @@ static NSVisualEffectBlendingMode VisualEffectBlendingModeForVibrancyType( } @implementation MOZVibrantView - - (instancetype)initWithFrame:(NSRect)aRect vibrancyType:(VibrancyType)aType { self = [super initWithFrame:aRect]; mType = aType; @@ -76,50 +63,31 @@ static NSVisualEffectBlendingMode VisualEffectBlendingModeForVibrancyType( return self; } -// Don't override allowsVibrancy here, because this view may have subviews, and -// returning YES from allowsVibrancy forces on foreground vibrancy for all -// descendant views, which can have unintended effects. - -@end - -@implementation MOZVibrantLeafView - - (NSView*)hitTest:(NSPoint)aPoint { // This view must be transparent to mouse events. return nil; } - -// MOZVibrantLeafView does not have subviews, so we can return YES here without -// having unintended effects on other contents of the window. -- (BOOL)allowsVibrancy { - return NO; -} - @end +VibrancyManager::VibrancyManager(const nsChildView& aCoordinateConverter, + NSView* aContainerView) + : mCoordinateConverter(aCoordinateConverter), + mContainerView(aContainerView) {} + +VibrancyManager::~VibrancyManager() = default; + bool VibrancyManager::UpdateVibrantRegion( VibrancyType aType, const LayoutDeviceIntRegion& aRegion) { + auto& slot = mVibrantRegions[aType]; if (aRegion.IsEmpty()) { - return mVibrantRegions.Remove(uint32_t(aType)); + bool hadRegion = !!slot; + slot = nullptr; + return hadRegion; } - auto& vr = *mVibrantRegions.GetOrInsertNew(uint32_t(aType)); - return vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() { - return CreateEffectView(aType); + if (!slot) { + slot = MakeUnique(); + } + return slot->UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() { + return [[MOZVibrantView alloc] initWithFrame:NSZeroRect vibrancyType:aType]; }); } - -LayoutDeviceIntRegion VibrancyManager::GetUnionOfVibrantRegions() const { - LayoutDeviceIntRegion result; - for (const auto& region : mVibrantRegions.Values()) { - result.OrWith(region->Region()); - } - return result; -} - -/* static */ NSView* VibrancyManager::CreateEffectView(VibrancyType aType, - BOOL aIsContainer) { - return aIsContainer ? [[MOZVibrantView alloc] initWithFrame:NSZeroRect - vibrancyType:aType] - : [[MOZVibrantLeafView alloc] initWithFrame:NSZeroRect - vibrancyType:aType]; -} diff --git a/widget/cocoa/ViewRegion.h b/widget/cocoa/ViewRegion.h index b2ed0c883515..d44bde5f71bf 100644 --- a/widget/cocoa/ViewRegion.h +++ b/widget/cocoa/ViewRegion.h @@ -8,6 +8,7 @@ #define ViewRegion_h #include "Units.h" +#include "nsRegion.h" #include "nsTArray.h" class nsChildView; diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index d3241a983fe8..a705888e4bfd 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -1726,32 +1726,52 @@ static Maybe ThemeGeometryTypeToVibrancyType( nsITheme::ThemeGeometryType aThemeGeometryType) { switch (aThemeGeometryType) { case eThemeGeometryTypeTitlebar: - return Some(VibrancyType::TITLEBAR); + return Some(VibrancyType::Titlebar); default: return Nothing(); } } -static LayoutDeviceIntRegion GatherVibrantRegion( - const nsTArray& aThemeGeometries, - VibrancyType aVibrancyType) { - LayoutDeviceIntRegion region; +static EnumeratedArray +GatherVibrantRegions(Span aThemeGeometries) { + EnumeratedArray regions; for (const auto& geometry : aThemeGeometries) { - if (ThemeGeometryTypeToVibrancyType(geometry.mType) == - Some(aVibrancyType)) { - region.OrWith(geometry.mRect); + auto vibrancyType = ThemeGeometryTypeToVibrancyType(geometry.mType); + if (!vibrancyType) { + continue; } + regions[*vibrancyType].OrWith(geometry.mRect); + } + return regions; +} + +// Subtracts parts from regions in such a way that they don't have any overlap. +// Each region in the argument list will have the union of all the regions +// *following* it subtracted from itself. In other words, the arguments are +// treated as low priority to high priority. +static void MakeRegionsNonOverlapping(Span aRegions) { + LayoutDeviceIntRegion unionOfAll; + for (auto& region : aRegions) { + region.SubOut(unionOfAll); + unionOfAll.OrWith(region); } - return region; } void nsChildView::UpdateVibrancy( const nsTArray& aThemeGeometries) { - LayoutDeviceIntRegion titlebarRegion = - GatherVibrantRegion(aThemeGeometries, VibrancyType::TITLEBAR); + auto regions = GatherVibrantRegions(aThemeGeometries); + MakeRegionsNonOverlapping(Span(regions.begin(), regions.end())); auto& vm = EnsureVibrancyManager(); - bool changed = vm.UpdateVibrantRegion(VibrancyType::TITLEBAR, titlebarRegion); + bool changed = false; + + // EnumeratedArray doesn't have an iterator that also yields the enum type, + // but we rely on VibrancyType being contiguous and starting at 0, so we can + // do that manually. + size_t i = 0; + for (const auto& region : regions) { + changed |= vm.UpdateVibrantRegion(VibrancyType(i++), region); + } if (changed) { SuspendAsyncCATransactions(); diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm index 8e54d9e7fde3..9e749fda1e3e 100644 --- a/widget/cocoa/nsCocoaWindow.mm +++ b/widget/cocoa/nsCocoaWindow.mm @@ -3071,10 +3071,6 @@ static NSMutableSet* gSwizzledFrameViewClasses = nil; - (void)_setNeedsDisplayInRect:(NSRect)aRect; @end -@interface NSView (NSVisualEffectViewSetMaskImage) -- (void)setMaskImage:(NSImage*)image; -@end - @interface BaseWindow (Private) - (void)removeTrackingArea; - (void)cursorUpdated:(NSEvent*)aEvent; @@ -3174,35 +3170,38 @@ static NSImage* GetMenuMaskImage() { return maskImage; } -- (void)swapOutChildViewWrapper:(NSView*)aNewWrapper { - aNewWrapper.frame = self.contentView.frame; +// Add an effect view wrapper if needed so that the OS draws the appropriate +// vibrancy effect and window border. +- (void)setEffectViewWrapperForStyle:(WindowShadow)aStyle { + NSView* wrapper = [&]() -> NSView* { + if (aStyle == WindowShadow::Menu || aStyle == WindowShadow::Tooltip) { + const bool isMenu = aStyle == WindowShadow::Menu; + auto* effectView = + [[NSVisualEffectView alloc] initWithFrame:self.contentView.frame]; + effectView.material = + isMenu ? NSVisualEffectMaterialMenu : NSVisualEffectMaterialToolTip; + // Tooltip and menu windows are never "key", so we need to tell the + // vibrancy effect to look active regardless of window state. + effectView.state = NSVisualEffectStateActive; + effectView.blendingMode = NSVisualEffectBlendingModeBehindWindow; + if (isMenu) { + // Turn on rounded corner masking. + effectView.maskImage = GetMenuMaskImage(); + } + return effectView; + } + return [[NSView alloc] initWithFrame:self.contentView.frame]; + }(); + + wrapper.wantsLayer = YES; + // Swap out our content view by the new view. Setting .contentView releases + // the old view. NSView* childView = [self.mainChildView retain]; [childView removeFromSuperview]; - [aNewWrapper addSubview:childView]; + [wrapper addSubview:childView]; [childView release]; - [super setContentView:aNewWrapper]; -} - -- (void)setEffectViewWrapperForStyle:(WindowShadow)aStyle { - if (aStyle == WindowShadow::Menu || aStyle == WindowShadow::Tooltip) { - // Add an effect view wrapper so that the OS draws the appropriate - // vibrancy effect and window border. - BOOL isMenu = aStyle == WindowShadow::Menu; - NSView* effectView = VibrancyManager::CreateEffectView( - isMenu ? VibrancyType::MENU : VibrancyType::TOOLTIP, YES); - if (isMenu) { - // Turn on rounded corner masking. - [effectView setMaskImage:GetMenuMaskImage()]; - } - [self swapOutChildViewWrapper:effectView]; - [effectView release]; - } else { - // Remove the existing wrapper. - NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect]; - [wrapper setWantsLayer:YES]; - [self swapOutChildViewWrapper:wrapper]; - [wrapper release]; - } + super.contentView = wrapper; + [wrapper release]; } - (NSTouchBar*)makeTouchBar {