blob: 357b0e41663da7f038a9b6137981f2a7f9301455 [file] [log] [blame]
// Copyright 2021-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreGraphics/CoreGraphics.h>
#import "MDCShadowsCollection.h"
#import "MDCAvailability.h"
#import "MDCShadow.h"
NS_ASSUME_NONNULL_BEGIN
@implementation MDCShadowsCollection {
NSDictionary<NSNumber *, MDCShadow *> *_shadowValuesForElevation;
NSArray<NSNumber *> *_orderedKeys;
}
- (instancetype)initWithShadowValuesForElevation:
(NSDictionary<NSNumber *, MDCShadow *> *)shadowValuesForElevation {
self = [super init];
if (self) {
_shadowValuesForElevation = [shadowValuesForElevation copy];
_orderedKeys = [shadowValuesForElevation.allKeys sortedArrayUsingSelector:@selector(compare:)];
}
return self;
}
- (MDCShadow *)shadowForElevation:(CGFloat)elevation {
NSUInteger lookupIndex = [self indexInOrderedKeysOfGivenElevation:elevation];
// If the value is larger than the largest value in the array, we will return the highest value in
// the array.
if (lookupIndex >= _orderedKeys.count) {
lookupIndex = _orderedKeys.count - 1;
}
NSNumber *key = _orderedKeys[lookupIndex];
return [_shadowValuesForElevation objectForKey:key];
}
- (NSUInteger)indexInOrderedKeysOfGivenElevation:(CGFloat)elevation {
NSNumber *num = @(elevation);
NSUInteger index =
[_orderedKeys indexOfObject:num
inSortedRange:NSMakeRange(0, _orderedKeys.count)
options:NSBinarySearchingInsertionIndex
usingComparator:^NSComparisonResult(NSNumber *num1, NSNumber *num2) {
return [num1 compare:num2];
}];
return index;
}
@end
@implementation MDCShadowsCollectionBuilder {
NSMutableDictionary<NSNumber *, MDCShadow *> *_shadowValuesForElevation;
}
- (instancetype)init {
self = [super init];
if (self) {
_shadowValuesForElevation = [[NSMutableDictionary alloc] init];
}
return self;
}
+ (MDCShadowsCollectionBuilder *)builderWithShadow:(MDCShadow *)shadow
forElevation:(CGFloat)elevation {
MDCShadowsCollectionBuilder *builder = [[self alloc] init];
[builder addShadow:shadow forElevation:elevation];
return builder;
}
- (void)addShadow:(MDCShadow *)shadow forElevation:(CGFloat)elevation {
[_shadowValuesForElevation setObject:shadow forKey:@(elevation)];
}
- (void)addShadowsForElevations:(NSDictionary<NSNumber *, MDCShadow *> *)shadowsForElevations {
[_shadowValuesForElevation addEntriesFromDictionary:shadowsForElevations];
}
- (MDCShadowsCollection *)build {
return [[MDCShadowsCollection alloc] initWithShadowValuesForElevation:_shadowValuesForElevation];
}
@end
static UIColor *LightStyleShadowColor(void) {
static UIColor *lightStyleShadowColor;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lightStyleShadowColor = [UIColor colorWithRed:0.235 green:0.251 blue:0.263 alpha:1];
});
return lightStyleShadowColor;
}
UIColor *MDCShadowColor(void) {
#if MDC_AVAILABLE_SDK_IOS(13_0)
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^(UITraitCollection *traitCollection) {
switch (traitCollection.userInterfaceStyle) {
case UIUserInterfaceStyleUnspecified:
/* FALLTHROUGH - TODO(b/185199658): Migrate to proper fallthrough logic */
case UIUserInterfaceStyleLight:
return LightStyleShadowColor();
case UIUserInterfaceStyleDark:
return UIColor.blackColor;
}
__builtin_unreachable();
}];
}
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
return LightStyleShadowColor();
}
MDCShadowsCollection *MDCShadowsCollectionDefault(void) {
static MDCShadowsCollection *shadowsCollection;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MDCShadow *shadow = [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0
radius:0
offset:CGSizeMake(0, 0)] build];
MDCShadowsCollectionBuilder *shadowsBuilder =
[MDCShadowsCollectionBuilder builderWithShadow:shadow forElevation:0];
NSDictionary<NSNumber *, MDCShadow *> *shadowValuesForElevation = @{
@1 : [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0.43
radius:2.5
offset:CGSizeMake(0, 1)] build],
@3 : [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0.4
radius:3.25
offset:CGSizeMake(0, 1.25)] build],
@6 : [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0.34
radius:4.75
offset:CGSizeMake(0, 2.25)] build],
@8 : [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0.42
radius:6
offset:CGSizeMake(0, 3)] build],
@12 : [[MDCShadowBuilder builderWithColor:MDCShadowColor()
opacity:0.4
radius:7.25
offset:CGSizeMake(0, 5)] build],
};
[shadowsBuilder addShadowsForElevations:shadowValuesForElevation];
shadowsCollection = [shadowsBuilder build];
});
return shadowsCollection;
}
void MDCConfigureShadowForView(UIView *view, MDCShadow *shadow) {
// The bezierPathWithRoundedRect API supports both a cornerRadius of 0 (created just a square
// path) and also rounded corners where the cornerRadius is >0.
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:view.bounds
cornerRadius:view.layer.cornerRadius];
if (shadow.spread > 0) {
CGRect spreadBounds = CGRectInset(view.bounds, -shadow.spread, -shadow.spread);
path = [UIBezierPath bezierPathWithRoundedRect:spreadBounds
cornerRadius:view.layer.cornerRadius + shadow.spread];
}
MDCConfigureShadowForViewWithPath(view, shadow, path.CGPath);
}
void MDCConfigureShadowForViewWithPath(UIView *view, MDCShadow *shadow, CGPathRef _Nullable path) {
UIColor *shadowColor = shadow.color;
#if MDC_AVAILABLE_SDK_IOS(13_0)
if (@available(ios 13.0, *)) {
shadowColor = [shadowColor resolvedColorWithTraitCollection:view.traitCollection];
}
#endif // MDC_AVAILABLE_SDK_IOS(13_0)
view.layer.shadowColor = shadowColor.CGColor;
view.layer.shadowOpacity = (float)shadow.opacity;
view.layer.shadowRadius = shadow.radius;
view.layer.shadowOffset = shadow.offset;
view.layer.shadowPath = path;
}
NS_ASSUME_NONNULL_END