diff --git a/CMakeLists.txt b/CMakeLists.txt index 99169a8f95..e26ebe2227 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -722,10 +722,12 @@ elseif(IOS) ios/PPSSPPUIApplication.mm ios/SmartKeyboardMap.cpp ios/SmartKeyboardMap.hpp + ios/SubtleVolume.h + ios/SubtleVolume.mm ios/iCade/iCadeReaderView.h ios/iCade/iCadeReaderView.m ios/iCade/iCadeState.h) - set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") endif() diff --git a/ios/SubtleVolume.h b/ios/SubtleVolume.h new file mode 100644 index 0000000000..642e5ef1f8 --- /dev/null +++ b/ios/SubtleVolume.h @@ -0,0 +1,83 @@ +// +// SubtleVolume.h +// subtleVolumeObjC +// +// Created by iMokhles on 24/03/16. +// Copyright © 2016 iMokhles. All rights reserved. +// + +#import +#import +#import + +/** + The style of the volume indicator + - Plain: A plain bar + - RoundedLine: A plain bar with rounded corners + - Dashes: A bar divided in dashes + - Dots: A bar composed by a line of dots + */ +typedef NS_ENUM(NSInteger, SubtleVolumeStyle) { + SubtleVolumeStylePlain, + SubtleVolumeStyleRoundedLine, + SubtleVolumeStyleDashes, + SubtleVolumeStyleDots +}; + + +/** + The entry and exit animation of the volume indicator + - None: The indicator is always visible + - SlideDown: The indicator fades in/out and slides from/to the top into position + - FadeIn: The indicator fades in and out + */ +typedef NS_ENUM(NSInteger, SubtleVolumeAnimation) { + SubtleVolumeAnimationNone, + SubtleVolumeAnimationSlideDown, + SubtleVolumeAnimationFadeIn +}; + +@class SubtleVolume; +/** + Delegate protocol fo `SubtleVolume`. + Notifies the delegate when a change is about to happen (before the entry animation) + and when a change occurred (and the exit animation is complete) + */ +@protocol SubtleVolumeDelegate +/** + The volume is about to change. This is fired before performing any entry animation + - parameter subtleVolume: The current instance of `SubtleVolume` + - parameter value: The value of the volume (between 0 an 1.0) + */ +- (void)subtleVolume:(SubtleVolume *)volumeView willChange:(CGFloat)value; +/** + The volume did change. This is fired after the exit animation is done + - parameter subtleVolume: The current instance of `SubtleVolume` + - parameter value: The value of the volume (between 0 an 1.0) + */ +- (void)subtleVolume:(SubtleVolume *)volumeView didChange:(CGFloat)value; + +@end + +/** + Replace the system volume popup with a more subtle way to display the volume + when the user changes it with the volume rocker. + */ +@interface SubtleVolume : UIView +/** + The style of the volume indicator + */ +@property (nonatomic, assign) SubtleVolumeStyle style; +/** + The entry and exit animation of the indicator. The animation is triggered by the volume + If the animation is set to `SubtleVolumeAnimationNone`, the volume indicator is always visible + */ +@property (nonatomic, assign) SubtleVolumeAnimation animation; +@property (nonatomic, strong) UIColor *barBackgroundColor; +@property (nonatomic, strong) UIColor *barTintColor; +@property (nonatomic, assign) id delegate; +@property (nonatomic, assign) BOOL animatedByDefault; + +- (instancetype)initWithStyle:(SubtleVolumeStyle)style; +- (instancetype)initWithStyle:(SubtleVolumeStyle)style frame:(CGRect)frame; +@end diff --git a/ios/SubtleVolume.mm b/ios/SubtleVolume.mm new file mode 100644 index 0000000000..80de327df3 --- /dev/null +++ b/ios/SubtleVolume.mm @@ -0,0 +1,250 @@ +// +// SubtleVolume.m +// subtleVolumeObjC +// +// Created by iMokhles on 24/03/16. +// Copyright © 2016 iMokhles. All rights reserved. +// + +#import "SubtleVolume.h" + +MPVolumeView *volume = [[MPVolumeView alloc] initWithFrame:CGRectZero]; +UIView *overlay = [[UIView alloc] init]; +CGFloat volumeLevel = 0; + +@interface SubtleVolume (){ + BOOL runningShowAnimation; + BOOL showing; + BOOL runningHideAnimation; + BOOL lastAnimated; +}; + +@property (nonatomic, strong) NSTimer *timer; + +- (void)timerComplete; +- (void)doHide:(BOOL)animated; +- (void)doShow:(BOOL)animated; +- (void)stopAnimations; + +@end + + +@implementation SubtleVolume + +- (instancetype)initWithStyle:(SubtleVolumeStyle)style frame:(CGRect)frame { + + self = [super initWithFrame:frame]; + + if (self) { + self.animatedByDefault = YES; + self.style = style; + [self setup]; + } + return self; +} + +- (instancetype)initWithStyle:(SubtleVolumeStyle)style { + return [self initWithStyle:style frame:CGRectZero]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self setup]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + + self = [super initWithFrame:frame]; + + if (self) { + [self setup]; + } + return self; +} + +- (instancetype)init { + self = nil; + NSAssert(false, @"To init this class please use the designated initializer: initWithStyle or initWithStyle:frame:"); + return nil; +} + +- (void)setup { + @try { + [[AVAudioSession sharedInstance] setActive:YES error:nil]; + } @catch (NSException *e) { + NSLog(@"Unable to initialize AVAudioSession"); + } + + volumeLevel = [[AVAudioSession sharedInstance] outputVolume]; +// [self updateVolume:[[AVAudioSession sharedInstance] outputVolume] animated:NO]; + [[AVAudioSession sharedInstance] addObserver:self forKeyPath:@"outputVolume" options:NSKeyValueObservingOptionNew context:NULL]; + [volume setVolumeThumbImage:[[UIImage alloc] init] forState:UIControlStateNormal]; + [volume setUserInteractionEnabled:NO]; + [volume setAlpha:0.0001]; + [volume setShowsRouteButton:NO]; + self.alpha = 0.0001; + + [self addSubview:volume]; + + [self addSubview:overlay]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + overlay.frame = self.frame; + overlay.frame = CGRectMake(0, 0, self.frame.size.width*volumeLevel, self.frame.size.height); + + self.backgroundColor = self.barBackgroundColor; + overlay.backgroundColor = self.barTintColor; + +} +- (void)updateVolume:(CGFloat)value animated:(BOOL)animated { + NSLog(@"updateVolume: value:%f animated:%@", value, animated?@"YES":@"NO"); + [self.delegate subtleVolume:self willChange:value]; + volumeLevel = value; + lastAnimated = animated; + [UIView animateWithDuration:(animated ? 0.1 : 0) animations:^{ + CGRect rectOverlayView = overlay.frame; + CGFloat overlyWidth = self.frame.size.width * volumeLevel; + rectOverlayView.size.width = overlyWidth; + overlay.frame = rectOverlayView; + }]; + + + if(self.timer) { + [self.timer invalidate]; + self.timer = nil; + } + + NSLog(@"Spinning up timer with timeInterval 2 ..."); + self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerComplete) userInfo:nil repeats:NO]; + + [self doShow:animated]; + + [self.delegate subtleVolume:self didChange:value]; +} + +- (void)timerComplete { + NSLog(@"timerComplete!"); + [self doHide:lastAnimated]; + self.timer = nil; +} + +- (void)doHide:(BOOL)animated { + NSLog(@"doHide:%@, runningShowAnimation: %@, runningHideAnimation: %@, showing: %@", + animated?@"YES":@"NO", + runningShowAnimation?@"YES":@"NO", + runningHideAnimation?@"YES":@"NO", + showing?@"YES":@"NO"); + + if(!showing) { + return; + } + + if(runningHideAnimation && !animated) { + [self stopAnimations]; + } + + if(runningHideAnimation) { + return; + } + + if(animated) { + runningHideAnimation = YES; + [UIView animateWithDuration:0.333 animations:^{ + switch (self.animation) { + case SubtleVolumeAnimationNone: + break; + case SubtleVolumeAnimationFadeIn: + self.alpha = 0.0001; + break; + case SubtleVolumeAnimationSlideDown: + self.alpha = 0.0001; + self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height); + break; + default: + break; + } + } completion:^(BOOL finished) { + NSLog(@"Hide animation complete; finished? %@", finished?@"YES":@"NO"); + showing = NO; + runningHideAnimation = NO; + }]; + } else { + NSLog(@"Hide immediate complete."); + showing = NO; + self.alpha = 0.0001; + if(self.animation == SubtleVolumeAnimationSlideDown) { + self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height); + } + } +} + +- (void)doShow:(BOOL)animated { + NSLog(@"doShow:%@, runningShowAnimation: %@, runningHideAnimation: %@, showing: %@", + animated?@"YES":@"NO", + runningShowAnimation?@"YES":@"NO", + runningHideAnimation?@"YES":@"NO", + showing?@"YES":@"NO"); + + if(showing) { + return; + } + + if(runningShowAnimation && !animated) { + [self stopAnimations]; + } + + if(runningShowAnimation) { + return; + } + + if(animated) { + runningShowAnimation = YES; + [UIView animateWithDuration:0.333 animations:^{ + switch (self.animation) { + case SubtleVolumeAnimationNone: + break; + case SubtleVolumeAnimationFadeIn: + self.alpha = 1; + break; + case SubtleVolumeAnimationSlideDown: + self.alpha = 1; + self.transform = CGAffineTransformIdentity; + break; + default: + break; + } + } completion:^(BOOL finished) { + NSLog(@"Show animation complete; finished? %@", finished?@"YES":@"NO"); + showing = YES; + runningShowAnimation = NO; + }]; + } else { + NSLog(@"Show immediate complete."); + showing = YES; + self.alpha = 1; + self.transform = CGAffineTransformIdentity; + } +} + +- (void)stopAnimations { + [self.layer removeAllAnimations]; + runningHideAnimation = NO; + runningShowAnimation = NO; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqual:@"outputVolume"]) { + CGFloat value = [change[@"new"] floatValue]; + [self updateVolume:value animated:self.animatedByDefault]; + } else { + return; + } + +} + +@end diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 4f7dcaad16..f57048410b 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -6,6 +6,7 @@ // #import "ViewController.h" +#import "SubtleVolume.h" #import #include @@ -81,8 +82,7 @@ static bool threadStopped = false; __unsafe_unretained ViewController* sharedViewController; static GraphicsContext *graphicsContext; -@interface ViewController () -{ +@interface ViewController () { std::map iCadeToKeyMap; } @@ -95,6 +95,12 @@ static GraphicsContext *graphicsContext; @end +@interface ViewController () { + SubtleVolume *volume; +} +@end + + @implementation ViewController -(id) init { @@ -129,6 +135,13 @@ static GraphicsContext *graphicsContext; return self; } +- (void)subtleVolume:(SubtleVolume *)volumeView willChange:(CGFloat)value { +// NSLog(@"%f alpha: %f", value, volumeView.alpha); +} +- (void)subtleVolume:(SubtleVolume *)volumeView didChange:(CGFloat)value { +// NSLog(@"END %f alpha: %f", value, volumeView.alpha); +} + - (void)viewDidLoad { [super viewDidLoad]; @@ -197,6 +210,15 @@ static GraphicsContext *graphicsContext; } #endif + volume = [[SubtleVolume alloc] initWithStyle:SubtleVolumeStylePlain frame:CGRectMake(10, 0, self.view.frame.size.width-20, 4)]; + // volume.animatedByDefault = NO; + volume.barTintColor = [UIColor whiteColor]; + volume.barBackgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3]; + volume.animation = SubtleVolumeAnimationSlideDown; + volume.delegate = self; + [self.view addSubview:volume]; + [self.view bringSubviewToFront:volume]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NativeInitGraphics(graphicsContext);