| // 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 |