mirror of
https://github.com/textmate/textmate.git
synced 2026-04-28 03:00:34 -04:00
Use Core Animation for find indicator
This commit is contained in:
committed by
Allan Odgaard
parent
ba8b6d52f4
commit
dd3ebefe3f
@@ -1,134 +1,136 @@
|
||||
#import "OakPopOutAnimation.h"
|
||||
#import <oak/algorithm.h>
|
||||
#import <oak/debug.h>
|
||||
|
||||
static CGFloat const kExtendWidth = 6;
|
||||
static CGFloat const kExtendWidth = 4;
|
||||
static CGFloat const kExtendHeight = 1;
|
||||
static CGFloat const kRectXRadius = 6;
|
||||
static CGFloat const kRectYRadius = 6;
|
||||
static double const kGrowDuration = 0.10;
|
||||
static double const kFadeDuration = 0.60;
|
||||
static CGFloat const kRectXRadius = 2;
|
||||
static CGFloat const kRectYRadius = 2;
|
||||
static CGFloat const kMaxScale = 1.5;
|
||||
static CGFloat const kShadowRadius = 2;
|
||||
static double const kGrowDuration = 0.05;
|
||||
static double const kFadeDuration = 0.50;
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_10_12) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12)
|
||||
@interface OakPopOutView : NSView
|
||||
#else
|
||||
@interface OakPopOutView : NSView <CAAnimationDelegate>
|
||||
#endif
|
||||
{
|
||||
OBJC_WATCH_LEAKS(OakPopOutView);
|
||||
|
||||
NSRect baseFrame;
|
||||
double growDuration;
|
||||
double fadeDuration;
|
||||
CALayer* imageLayer;
|
||||
CAShapeLayer* shapeLayer;
|
||||
}
|
||||
@property (nonatomic) NSDate* animationStartTime;
|
||||
@property (nonatomic) NSImage* contentImage;
|
||||
@property (nonatomic) NSWindow* retainedWindow;
|
||||
- (void)startAnimation:(id)sender;
|
||||
@end
|
||||
|
||||
void OakShowPopOutAnimation (NSRect aRect, NSImage* anImage)
|
||||
void OakShowPopOutAnimation (NSRect viewRect, NSImage* anImage)
|
||||
{
|
||||
if(aRect.size.width == 0 || aRect.size.height == 0)
|
||||
if(viewRect.size.width == 0 || viewRect.size.height == 0)
|
||||
return;
|
||||
|
||||
aRect = NSInsetRect(aRect, -kExtendWidth, -kExtendHeight);
|
||||
NSRect contentRect = NSMakeRect(0, 0, NSWidth(aRect), NSHeight(aRect));
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
|
||||
[window setAlphaValue:1];
|
||||
viewRect = NSInsetRect(viewRect, -kExtendWidth, -kExtendHeight);
|
||||
NSRect windowRect = viewRect;
|
||||
CGFloat extraWidth = ceil((kMaxScale - 1) * (viewRect.size.width + 4 * kShadowRadius)/2);
|
||||
CGFloat extraHeight = ceil((kMaxScale - 1) * (viewRect.size.height + 4 * kShadowRadius)/2);
|
||||
windowRect.origin.x -= extraWidth; viewRect.origin.x = extraWidth;
|
||||
windowRect.origin.y -= extraHeight; viewRect.origin.y = extraHeight;
|
||||
windowRect.size.width += 2 * extraWidth;
|
||||
windowRect.size.height += 2 * extraHeight;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:windowRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
|
||||
[window setBackgroundColor:[NSColor clearColor]];
|
||||
[window setExcludedFromWindowsMenu:YES];
|
||||
[window setHasShadow:YES];
|
||||
[window setIgnoresMouseEvents:YES];
|
||||
[window setLevel:NSStatusWindowLevel];
|
||||
[window setOpaque:NO];
|
||||
[window setReleasedWhenClosed:NO];
|
||||
[window useOptimizedDrawing:YES];
|
||||
[[window contentView] setWantsLayer:YES];
|
||||
|
||||
OakPopOutView* aView = [[OakPopOutView alloc] initWithFrame:contentRect];
|
||||
OakPopOutView* aView = [[OakPopOutView alloc] initWithFrame:viewRect];
|
||||
[aView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
|
||||
aView.contentImage = anImage;
|
||||
aView.retainedWindow = window;
|
||||
[[window contentView] addSubview:aView];
|
||||
|
||||
[window setFrame:aRect display:YES];
|
||||
[window setFrame:[window frameRectForContentRect:windowRect] display:YES];
|
||||
[window orderFront:nil];
|
||||
|
||||
[aView startAnimation:nil];
|
||||
}
|
||||
|
||||
static double bounce_curve (double t)
|
||||
{
|
||||
return 1 - sqrt( 1 - pow((1 - fabs(2*t - 1)), 2) );
|
||||
// return 1 - fabs( 2 * (1 - (1.0 / (1.0 + exp(-12*t + 6)))) - 1);
|
||||
}
|
||||
|
||||
@implementation OakPopOutView
|
||||
- (id)initWithFrame:(NSRect)aRect
|
||||
{
|
||||
if(self = [super initWithFrame:aRect])
|
||||
{
|
||||
double const slowDownFactor = 1; // ([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSAlternateKeyMask|NSControlKeyMask|NSShiftKeyMask)) == NSShiftKeyMask ? 6 : 1;
|
||||
growDuration = kGrowDuration * slowDownFactor;
|
||||
fadeDuration = kFadeDuration * slowDownFactor;
|
||||
[self setWantsLayer:YES];
|
||||
self.layer.masksToBounds = NO;
|
||||
|
||||
shapeLayer = [CAShapeLayer layer];
|
||||
shapeLayer.frame = self.bounds;
|
||||
shapeLayer.fillColor = [[NSColor yellowColor] CGColor];
|
||||
shapeLayer.strokeColor = [[NSColor colorWithWhite:0 alpha:0.1] CGColor];
|
||||
shapeLayer.lineWidth = 0.5;
|
||||
shapeLayer.path = CGPathCreateWithRoundedRect(CGRectInset([self bounds], 0.25, 0.25), kRectXRadius, kRectYRadius, NULL);
|
||||
shapeLayer.shadowOpacity = 0.25;
|
||||
shapeLayer.shadowRadius = kShadowRadius;
|
||||
shapeLayer.shadowOffset = CGSizeMake(0, -1);
|
||||
[self.layer addSublayer:shapeLayer];
|
||||
|
||||
imageLayer = [CALayer layer];
|
||||
[shapeLayer addSublayer:imageLayer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
CGFloat scaleFactor = self.window.screen.backingScaleFactor;
|
||||
if(scaleFactor)
|
||||
{
|
||||
imageLayer.contents = [_contentImage layerContentsForContentsScale:scaleFactor];
|
||||
imageLayer.bounds = CGRectMake(0, 0, _contentImage.size.width, _contentImage.size.height);
|
||||
imageLayer.position = CGPointMake(CGRectGetMidX(shapeLayer.bounds), CGRectGetMidY(shapeLayer.bounds));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startAnimation:(id)sender
|
||||
{
|
||||
baseFrame = [[self window] frame];
|
||||
self.animationStartTime = [NSDate date];
|
||||
[NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(animationTick:) userInfo:nil repeats:YES];
|
||||
static CAAnimationGroup* animationGroup; // Animations are copied; we'll reuse one
|
||||
static dispatch_once_t onceToken = 0;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CABasicAnimation* grow = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
||||
grow.duration = kGrowDuration;
|
||||
grow.fromValue = @1;
|
||||
grow.toValue = @(kMaxScale);
|
||||
grow.autoreverses = YES;
|
||||
grow.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
|
||||
|
||||
CABasicAnimation* fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fade.beginTime = 2 * kGrowDuration;
|
||||
fade.duration = kFadeDuration;
|
||||
fade.fromValue = @1;
|
||||
fade.toValue = @0;
|
||||
fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
|
||||
animationGroup = [CAAnimationGroup new];
|
||||
animationGroup.animations = @[grow, fade];
|
||||
animationGroup.duration = 2 * kGrowDuration + kFadeDuration;
|
||||
animationGroup.fillMode = kCAFillModeForwards;
|
||||
animationGroup.removedOnCompletion = NO;
|
||||
});
|
||||
|
||||
animationGroup.delegate = self; // Listen for animationDidStop:finished:
|
||||
animationGroup.speed = 1;
|
||||
[shapeLayer addAnimation:animationGroup forKey:nil];
|
||||
animationGroup.delegate = nil;
|
||||
}
|
||||
|
||||
- (void)animationTick:(NSTimer*)aTimer
|
||||
- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag
|
||||
{
|
||||
double const totalDuration = 2*growDuration + fadeDuration;
|
||||
|
||||
CGFloat alpha = 1.0;
|
||||
CGFloat grow = 0.0;
|
||||
|
||||
double t = [[NSDate date] timeIntervalSinceDate:self.animationStartTime];
|
||||
if(t > totalDuration)
|
||||
{
|
||||
[aTimer invalidate];
|
||||
[[self window] orderOut:self];
|
||||
self.retainedWindow = nil;
|
||||
return;
|
||||
}
|
||||
else if(t > 2*growDuration)
|
||||
{
|
||||
t = (t - 2*growDuration) * 1.0/fadeDuration;
|
||||
alpha = 0.97 * (1 - oak::slow_in_out(t));
|
||||
}
|
||||
else
|
||||
{
|
||||
t = t * 1.0/(2*growDuration);
|
||||
grow = bounce_curve(t);
|
||||
}
|
||||
|
||||
grow = 1 + grow / 2;
|
||||
CGFloat w = NSWidth(baseFrame) * grow;
|
||||
CGFloat h = NSHeight(baseFrame) * grow;
|
||||
CGFloat x = round(NSMidX(baseFrame) - w/2);
|
||||
CGFloat y = round(NSMidY(baseFrame) - h/2);
|
||||
[[self window] setFrame:NSMakeRect(x, y, round(w), round(h)) display:YES];
|
||||
[[self window] setAlphaValue:alpha];
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped { return YES; }
|
||||
- (BOOL)isOpaque { return NO; }
|
||||
|
||||
- (void)drawRect:(NSRect)aRect
|
||||
{
|
||||
[[NSColor clearColor] set];
|
||||
NSRectFill(aRect);
|
||||
|
||||
NSBezierPath* roundedRect = [NSBezierPath bezierPathWithRoundedRect:[self bounds] xRadius:kRectXRadius yRadius:kRectYRadius];
|
||||
[[NSColor yellowColor] set];
|
||||
[roundedRect fill];
|
||||
[[NSColor whiteColor] set];
|
||||
[roundedRect stroke];
|
||||
|
||||
[self.contentImage drawInRect:NSInsetRect([self bounds], kExtendWidth, kExtendHeight) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1 respectFlipped:YES hints:nil];
|
||||
|
||||
[super drawRect:aRect];
|
||||
[[self window] orderOut:self];
|
||||
self.retainedWindow = nil;
|
||||
}
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user