See https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#trailing-whitespace
146 lines
4.7 KiB
Objective-C
146 lines
4.7 KiB
Objective-C
#import "MBXFrameTimeGraphView.h"
|
|
|
|
const CGFloat MBXFrameTimeExaggeration = 10.f;
|
|
const CGFloat MBXFrameTimeBarWidth = 4.f;
|
|
|
|
@interface MBXFrameTimeGraphView ()
|
|
|
|
@property (nonatomic) CAScrollLayer *scrollLayer;
|
|
@property (nonatomic) CAShapeLayer *thresholdLayer;
|
|
@property (nonatomic) CGFloat currentX;
|
|
@property (nonatomic) NSMutableArray<CAShapeLayer *> *barLayers;
|
|
|
|
@property (nonatomic) UIColor *safeColor;
|
|
@property (nonatomic) UIColor *warningColor;
|
|
@property (nonatomic) UIColor *dangerColor;
|
|
|
|
@end
|
|
|
|
@implementation MBXFrameTimeGraphView
|
|
|
|
- (instancetype)init {
|
|
if (self = [super init]) {
|
|
[self commonInit];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
|
if (self = [super initWithCoder:aDecoder]) {
|
|
[self commonInit];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)commonInit {
|
|
self.userInteractionEnabled = NO;
|
|
self.layer.opacity = 0.9f;
|
|
|
|
self.scrollLayer = [CAScrollLayer layer];
|
|
self.scrollLayer.scrollMode = kCAScrollHorizontally;
|
|
self.scrollLayer.masksToBounds = YES;
|
|
[self.layer addSublayer:self.scrollLayer];
|
|
|
|
self.thresholdLayer = [CAShapeLayer layer];
|
|
self.thresholdLayer.fillColor = [UIColor darkGrayColor].CGColor;
|
|
[self.layer addSublayer:self.thresholdLayer];
|
|
|
|
self.barLayers = [NSMutableArray array];
|
|
|
|
self.safeColor = [UIColor colorWithRed:(CGFloat)(0.f/255.f) green:(CGFloat)(190.f/255.f) blue:(CGFloat)(123.f/255.f) alpha:1.f];
|
|
self.warningColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(154.f/255.f) blue:(CGFloat)(82.f/255.f) alpha:1.f];
|
|
self.dangerColor = [UIColor colorWithRed:(CGFloat)(255.f/255.f) green:(CGFloat)(91.f/255.f) blue:(CGFloat)(86.f/255.f) alpha:1.f];
|
|
}
|
|
|
|
- (void)layoutSubviews {
|
|
[super layoutSubviews];
|
|
|
|
if (!CGRectEqualToRect(self.scrollLayer.frame, self.bounds)) {
|
|
self.scrollLayer.frame = self.bounds;
|
|
|
|
CGRect thresholdLineRect = CGRectMake(0, self.frame.size.height - self.frame.size.height * MBXFrameTimeExaggeration * [self renderDurationTargetMilliseconds], self.frame.size.width, 1);
|
|
|
|
{
|
|
CGPathRef path = CGPathCreateWithRect(thresholdLineRect, nil);
|
|
self.thresholdLayer.path = path;
|
|
CGPathRelease(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)updatePathWithFrameDuration:(CFTimeInterval)frameDuration {
|
|
[CATransaction begin];
|
|
[CATransaction setDisableActions:YES];
|
|
|
|
self.currentX += MBXFrameTimeBarWidth;
|
|
|
|
CAShapeLayer *bar = [self barWithFrameDuration:frameDuration];
|
|
bar.position = CGPointMake(self.currentX, self.frame.size.height);
|
|
|
|
[self.scrollLayer addSublayer:bar];
|
|
[self.barLayers addObject:bar];
|
|
|
|
[self.scrollLayer scrollToPoint:CGPointMake(self.currentX - self.frame.size.width, 0)];
|
|
|
|
[self removeStaleBars];
|
|
|
|
[CATransaction commit];
|
|
}
|
|
|
|
- (CGFloat)renderDurationTargetMilliseconds {
|
|
CGFloat maximumFramesPerSecond;
|
|
if (@available(iOS 10.3, *)) {
|
|
maximumFramesPerSecond = UIScreen.mainScreen.maximumFramesPerSecond;
|
|
} else {
|
|
// Not always strictly accurate, but works as an expedient approximation.
|
|
maximumFramesPerSecond = 60;
|
|
}
|
|
|
|
CGFloat target = (1.0 / maximumFramesPerSecond);
|
|
return target;
|
|
}
|
|
|
|
- (CGFloat)roundedFloat:(CGFloat)f {
|
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
|
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
|
|
#elif TARGET_OS_MAC
|
|
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
|
#endif
|
|
return round(f * scaleFactor) / scaleFactor;
|
|
}
|
|
|
|
- (CAShapeLayer *)barWithFrameDuration:(CFTimeInterval)frameDuration {
|
|
CAShapeLayer *bar = [CAShapeLayer layer];
|
|
|
|
CGRect barRect = CGRectMake(0, 0, MBXFrameTimeBarWidth, -(fminf(frameDuration * MBXFrameTimeExaggeration * self.frame.size.height, self.frame.size.height)));
|
|
UIBezierPath *barPath = [UIBezierPath bezierPathWithRect:barRect];
|
|
bar.path = barPath.CGPath;
|
|
bar.fillColor = [self colorForFrameDuration:frameDuration].CGColor;
|
|
|
|
return bar;
|
|
}
|
|
|
|
- (UIColor *)colorForFrameDuration:(CFTimeInterval)frameDuration {
|
|
const double greenLevel = [self renderDurationTargetMilliseconds];
|
|
const double yellowLevel = greenLevel * 3.0;
|
|
|
|
if (frameDuration <= greenLevel) {
|
|
return self.safeColor;
|
|
} else if (frameDuration <= yellowLevel) {
|
|
return self.warningColor;
|
|
} else {
|
|
return self.dangerColor;
|
|
}
|
|
}
|
|
|
|
- (void)removeStaleBars {
|
|
if (self.barLayers.count > (self.frame.size.width / MBXFrameTimeBarWidth * 3)) {
|
|
NSRange staleBarsRange = NSMakeRange(0, self.frame.size.width / MBXFrameTimeBarWidth);
|
|
NSArray *staleBars = [self.barLayers subarrayWithRange:staleBarsRange];
|
|
[staleBars makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
|
|
[self.barLayers removeObjectsInRange:staleBarsRange];
|
|
}
|
|
}
|
|
|
|
@end
|