blob: f1a11b5a2703839874ba26e4f87bd07709f03049 [file] [log] [blame]
/*
* Copyright (C) 2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if ENABLE(FULLSCREEN_API) && PLATFORM(IOS)
#include "WKFullscreenStackView.h"
#import "UIKitSPI.h"
#import <QuartzCore/CAFilter.h>
#import <UIKit/UIVisualEffectView.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/RetainPtr.h>
static NSArray<UIVisualEffect *>* reducedTransparencyEffects()
{
static NeverDestroyed<RetainPtr<NSArray<UIVisualEffect *>>> effects;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
effects.get() = @[[UIVisualEffect effectCompositingColor:[UIColor colorWithRed:(43.0 / 255.0) green:(46.0 / 255.0) blue:(48.0 / 255.0) alpha:1.0] withMode:UICompositingModeNormal alpha:1.0]];
});
return effects.get().get();
}
static NSArray<UIVisualEffect *>* normalTransparencyEffects()
{
static NeverDestroyed<RetainPtr<NSArray<UIVisualEffect *>>> effects;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
effects.get() = @[
[UIVisualEffect effectCompositingColor:[UIColor blackColor] withMode:UICompositingModeNormal alpha:0.55],
[UIBlurEffect effectWithBlurRadius:UIRoundToScreenScale(17.5, [UIScreen mainScreen])],
[UIColorEffect colorEffectSaturate:1.8],
[UIVisualEffect effectCompositingColor:[UIColor whiteColor] withMode:UICompositingModeNormal alpha:0.14]
];
});
return effects.get().get();
}
@interface UIVisualEffectView (WebKitPrivate)
@property (nonatomic, readwrite, copy) NSArray<UIVisualEffect *> *backgroundEffects;
@end
@interface WKFullscreenStackView ()
@property (nonatomic, readonly) UIStackView *_stackView;
@property (nonatomic, readonly) UIVisualEffectView *_visualEffectView;
@property (nonatomic) UIVisualEffectView *secondaryMaterialOverlayView;
@property (nonatomic) NSArray<NSLayoutConstraint *> *secondaryMaterialOverlayViewConstraints;
@end
@implementation WKFullscreenStackView
#pragma mark - Class Methods
+ (NSArray<UIVisualEffect *> *)baseEffects
{
if (UIAccessibilityIsReduceTransparencyEnabled())
return reducedTransparencyEffects();
return normalTransparencyEffects();
}
+ (void)configureView:(UIView *)view forTintEffectWithColor:(UIColor *)tintColor filterType:(NSString *)filterType
{
if ([view isKindOfClass:[UILabel class]]) {
[(UILabel *)view setTextColor:tintColor];
[view setTintColor:tintColor];
[[view layer] setCompositingFilter:[CAFilter filterWithType:kCAFilterPlusL]];
return;
}
_UIVisualEffectTintLayerConfig *tintLayerConfig = [_UIVisualEffectTintLayerConfig layerWithTintColor:tintColor filterType:filterType];
[[[_UIVisualEffectConfig configWithContentConfig:tintLayerConfig] contentConfig] configureLayerView:view];
}
+ (void)configureView:(UIView *)view withBackgroundFillOfColor:(UIColor *)fillColor opacity:(CGFloat)opacity filter:(NSString *)filter
{
_UIVisualEffectLayerConfig *baseLayerConfig = [_UIVisualEffectLayerConfig layerWithFillColor:fillColor opacity:opacity filterType:filter];
[[[_UIVisualEffectConfig configWithContentConfig:baseLayerConfig] contentConfig] configureLayerView:view];
}
+ (UIVisualEffectView *)secondaryMaterialOverlayView
{
UIVisualEffectView *secondaryMaterialOverlayView = [[UIVisualEffectView alloc] initWithEffect:nil];
[secondaryMaterialOverlayView setUserInteractionEnabled:NO];
[secondaryMaterialOverlayView setBackgroundEffects:@[[UIVisualEffect effectCompositingColor:[UIColor blackColor] withMode:UICompositingModePlusDarker alpha:0.06]]];
return secondaryMaterialOverlayView;
}
#pragma mark - External Interface
+ (void)applyPrimaryGlyphTintToView:(UIView *)view
{
[self configureView:view forTintEffectWithColor:[UIColor colorWithWhite:1.0 alpha:0.75] filterType:kCAFilterPlusL];
}
+ (void)applySecondaryGlyphTintToView:(UIView *)view
{
[self configureView:view forTintEffectWithColor:[UIColor colorWithWhite:1.0 alpha:0.55] filterType:kCAFilterPlusL];
}
- (instancetype)initWithArrangedSubviews:(NSArray<UIView *> *)arrangedSubviews axis:(UILayoutConstraintAxis)axis
{
self = [self initWithFrame:CGRectMake(0, 0, 100, 100)];
if (!self)
return nil;
[self setClipsToBounds:YES];
_visualEffectView = [[UIVisualEffectView alloc] initWithEffect:nil];
[self addSubview:_visualEffectView];
[_visualEffectView setBackgroundEffects:[[self class] baseEffects]];
_stackView = [[UIStackView alloc] initWithArrangedSubviews:arrangedSubviews];
[_stackView setAxis:axis];
[_stackView setLayoutMarginsRelativeArrangement:YES];
[_stackView setInsetsLayoutMarginsFromSafeArea:NO];
[self insertSubview:_stackView above:_visualEffectView];
[self _setArrangedSubviews:arrangedSubviews axis:axis];
return self;
}
- (void)setTargetViewForSecondaryMaterialOverlay:(UIView *)targetViewForSecondaryMaterialOverlay
{
if (_targetViewForSecondaryMaterialOverlay == targetViewForSecondaryMaterialOverlay)
return;
_targetViewForSecondaryMaterialOverlay = targetViewForSecondaryMaterialOverlay;
[self setNeedsUpdateConstraints];
}
- (UIView *)contentView
{
return [_visualEffectView contentView];
}
#pragma mark - Internal Interface
@synthesize _stackView=_stackView;
@synthesize _visualEffectView=_visualEffectView;
- (void)_setArrangedSubviews:(NSArray<UIView *> *)arrangedSubviews axis:(UILayoutConstraintAxis)axis
{
for (UIView *view in [_stackView arrangedSubviews])
[view removeFromSuperview];
for (UIView *view in arrangedSubviews)
[_stackView addArrangedSubview:view];
[_stackView setAxis:axis];
}
#pragma mark - UIView Overrides
- (void)setBounds:(CGRect)bounds
{
CGSize oldSize = [self bounds].size;
[super setBounds:bounds];
if (!CGSizeEqualToSize(oldSize, bounds.size))
[self _setContinuousCornerRadius:((CGRectGetHeight(bounds) > 40.0) ? 16.0 : 8.0)];
}
- (void)updateConstraints
{
if ([_stackView translatesAutoresizingMaskIntoConstraints] || [_visualEffectView translatesAutoresizingMaskIntoConstraints]) {
[_visualEffectView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_stackView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSArray<NSLayoutConstraint *> *constraints = @[
[[_visualEffectView leadingAnchor] constraintEqualToAnchor:[self leadingAnchor]],
[[_visualEffectView topAnchor] constraintEqualToAnchor:[self topAnchor]],
[[_visualEffectView trailingAnchor] constraintEqualToAnchor:[self trailingAnchor]],
[[_visualEffectView bottomAnchor] constraintEqualToAnchor:[self bottomAnchor]],
[[_stackView leadingAnchor] constraintEqualToAnchor:[self leadingAnchor]],
[[_stackView topAnchor] constraintEqualToAnchor:[self topAnchor]],
[[_stackView trailingAnchor] constraintEqualToAnchor:[self trailingAnchor]],
[[_stackView bottomAnchor] constraintEqualToAnchor:[self bottomAnchor]],
];
for (NSLayoutConstraint *constraint in constraints)
[constraint setPriority:(UILayoutPriorityRequired - 1)];
[NSLayoutConstraint activateConstraints:constraints];
}
if ([[self targetViewForSecondaryMaterialOverlay] isDescendantOfView:self]) {
if (!_secondaryMaterialOverlayView) {
[self setSecondaryMaterialOverlayView:[[self class] secondaryMaterialOverlayView]];
[self addSubview:[self secondaryMaterialOverlayView]];
}
if ([[self secondaryMaterialOverlayView] isDescendantOfView:self] && !_secondaryMaterialOverlayViewConstraints) {
[[self secondaryMaterialOverlayView] setTranslatesAutoresizingMaskIntoConstraints:NO];
NSArray<NSLayoutConstraint *> *constraints = @[
[[_secondaryMaterialOverlayView centerXAnchor] constraintEqualToAnchor:[_targetViewForSecondaryMaterialOverlay centerXAnchor]],
[[_secondaryMaterialOverlayView centerYAnchor] constraintEqualToAnchor:[_targetViewForSecondaryMaterialOverlay centerYAnchor]],
[[_secondaryMaterialOverlayView widthAnchor] constraintEqualToAnchor:[_targetViewForSecondaryMaterialOverlay widthAnchor]],
[[_secondaryMaterialOverlayView heightAnchor] constraintEqualToAnchor:[_targetViewForSecondaryMaterialOverlay heightAnchor]]
];
[self setSecondaryMaterialOverlayViewConstraints:constraints];
[NSLayoutConstraint activateConstraints:[self secondaryMaterialOverlayViewConstraints]];
}
} else
[_secondaryMaterialOverlayView removeFromSuperview];
[super updateConstraints];
}
@end
#endif // ENABLE(FULLSCREEN_API) && PLATFORM(IOS)