Bug 1891354 - Clean up VibrancyManager a bit. r=sam,mac-reviewers,bradwerth

Make the tooltip menupopup view not go through VibrancyManager at all
(don't really need to).

Use an EnumeratedArray for storage rather than a hashmap.

Differential Revision: https://phabricator.services.mozilla.com/D207390
This commit is contained in:
Emilio Cobos Álvarez 2024-04-15 15:55:44 +00:00
parent 9d3a3d6b34
commit cd7d835e04
5 changed files with 99 additions and 127 deletions

View File

@ -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 <Foundation/NSGeometry.h>
@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<VibrancyType> {
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<nsUint32HashKey, ViewRegion> mVibrantRegions;
EnumeratedArray<VibrancyType, UniquePtr<ViewRegion>> mVibrantRegions;
};
} // namespace mozilla

View File

@ -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 <objc/message.h>
@ -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<ViewRegion>();
}
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];
}

View File

@ -8,6 +8,7 @@
#define ViewRegion_h
#include "Units.h"
#include "nsRegion.h"
#include "nsTArray.h"
class nsChildView;

View File

@ -1726,32 +1726,52 @@ static Maybe<VibrancyType> ThemeGeometryTypeToVibrancyType(
nsITheme::ThemeGeometryType aThemeGeometryType) {
switch (aThemeGeometryType) {
case eThemeGeometryTypeTitlebar:
return Some(VibrancyType::TITLEBAR);
return Some(VibrancyType::Titlebar);
default:
return Nothing();
}
}
static LayoutDeviceIntRegion GatherVibrantRegion(
const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
VibrancyType aVibrancyType) {
LayoutDeviceIntRegion region;
static EnumeratedArray<VibrancyType, LayoutDeviceIntRegion>
GatherVibrantRegions(Span<const nsIWidget::ThemeGeometry> aThemeGeometries) {
EnumeratedArray<VibrancyType, LayoutDeviceIntRegion> 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<LayoutDeviceIntRegion> aRegions) {
LayoutDeviceIntRegion unionOfAll;
for (auto& region : aRegions) {
region.SubOut(unionOfAll);
unionOfAll.OrWith(region);
}
return region;
}
void nsChildView::UpdateVibrancy(
const nsTArray<ThemeGeometry>& 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();

View File

@ -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 {