From b52866b053b49238bb536324e8a1a1fce859e49e Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Fri, 19 Mar 2010 21:33:33 +0000 Subject: [Author: dmaclach] This may help clean up some code in chrome, and make doing animations on the mac easier in general. Not really sure how to unit test effectively, but I'll try and come up with something this evening. Hopefully you will get a chance to look at it, so Andy can use it for his current CL in flight http://codereview.chromium.org/1118004. R=thomasvl DELTA=908 (908 added, 0 deleted, 0 changed) --- AppKit/GTMNSAnimatablePropertyContainer.h | 49 +++ AppKit/GTMNSAnimatablePropertyContainer.m | 124 +++++++ AppKit/GTMNSAnimatablePropertyContainerTest.h | 51 +++ AppKit/GTMNSAnimatablePropertyContainerTest.m | 237 +++++++++++++ AppKit/GTMNSAnimatablePropertyContainerTest.xib | 426 ++++++++++++++++++++++++ 5 files changed, 887 insertions(+) create mode 100644 AppKit/GTMNSAnimatablePropertyContainer.h create mode 100644 AppKit/GTMNSAnimatablePropertyContainer.m create mode 100644 AppKit/GTMNSAnimatablePropertyContainerTest.h create mode 100644 AppKit/GTMNSAnimatablePropertyContainerTest.m create mode 100644 AppKit/GTMNSAnimatablePropertyContainerTest.xib (limited to 'AppKit') diff --git a/AppKit/GTMNSAnimatablePropertyContainer.h b/AppKit/GTMNSAnimatablePropertyContainer.h new file mode 100644 index 0000000..c98cabe --- /dev/null +++ b/AppKit/GTMNSAnimatablePropertyContainer.h @@ -0,0 +1,49 @@ +// +// GTMNSAnimatablePropertyContainer.h +// +// Copyright (c) 2010 Google Inc. 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 +#import "GTMDefines.h" + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + +// There is a bug in 10.5 where you cannot stop an animation on a +// NSAnimatablePropertyContainer by just setting it's duration to 0.0. +// The work around is rather complex requiring you to NULL out animation +// dictionary entries temporarily (see the code for details). +// These categories are to make stopping animations simpler. +// When you want to stop an animation, you just call it like you would +// an animator. +// +// [[myWindow gtm_animatorStopper] setAlphaValue:0.0]; +// +// This will stop any current animations that are going on, and will immediately +// set the alpha value of the window to 0. +// If there is no animation, it will still set the alpha value to 0.0 for you. +@interface NSView (GTMNSAnimatablePropertyContainer) + +- (id)gtm_animatorStopper; + +@end + +@interface NSWindow (GTMNSAnimatablePropertyContainer) + +- (id)gtm_animatorStopper; + +@end + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 diff --git a/AppKit/GTMNSAnimatablePropertyContainer.m b/AppKit/GTMNSAnimatablePropertyContainer.m new file mode 100644 index 0000000..a47ff65 --- /dev/null +++ b/AppKit/GTMNSAnimatablePropertyContainer.m @@ -0,0 +1,124 @@ +// +// GTMNSAnimatablePropertyContainer.m +// +// Copyright (c) 2010 Google Inc. 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 "GTMNSAnimatablePropertyContainer.h" + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + +@interface GTMAnimatorStopper : NSObject { + @private + NSObject *container_; +} +@end + +@implementation GTMAnimatorStopper +- (id)initWithAnimatablePropertyContainer:(NSObject*) container { + if ((self = [super init])) { + container_ = [container retain]; + } + return self; +} + +- (void)dealloc { + [container_ release]; + [super dealloc]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + return [container_ methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + SEL selector = [anInvocation selector]; + NSString *selectorName = NSStringFromSelector(selector); + + // NSWindow animator handles setFrame:display: which is an odd case + // for animator. All other methods take just a key value, so we convert + // this to it's equivalent key value. + if ([selectorName isEqual:@"setFrame:display:"]) { + selectorName = @"setFrame:"; + } + + // Check to make sure our selector is valid (starts with set and has a + // single : at the end. + NSRange colonRange = [selectorName rangeOfString:@":"]; + NSUInteger selectorLength = [selectorName length]; + if ([selectorName hasPrefix:@"set"] + && colonRange.location == selectorLength - 1 + && selectorLength > 4) { + // transform our selector into a keyValue by removing the set + // and the colon and converting the first char down to lowercase. + NSString *keyValue = [selectorName substringFromIndex:3]; + NSString *firstChar = [[keyValue substringToIndex:1] lowercaseString]; + NSRange rest = NSMakeRange(1, [keyValue length] - 2); + NSString *restOfKey = [keyValue substringWithRange:rest]; + keyValue = [firstChar stringByAppendingString:restOfKey]; + + // Save a copy of our old animations. + NSDictionary *oldAnimations + = [[[container_ animations] copy] autorelease]; + + // For frame the animator doesn't actually animate the rect but gets + // animators for the size and the origin independently. In case this changes + // in the future (similar to bounds), we will stop the animations for the + // frame as well as the frameSize and frameOrigin. + NSDictionary *animations = nil; + NSNull *null = [NSNull null]; + if ([keyValue isEqual:@"frame"]) { + animations = [NSDictionary dictionaryWithObjectsAndKeys: + null, @"frame", + null, @"frameSize", + null, @"frameOrigin", nil]; + } else { + animations = [NSDictionary dictionaryWithObject:null forKey:keyValue]; + } + + // Set our animations to NULL which will force them to stop. + [container_ setAnimations:animations]; + // Call our original invocation on our animator. + [anInvocation setTarget:[container_ animator]]; + [anInvocation invoke]; + + // Reset the animations. + [container_ setAnimations:oldAnimations]; + } else { + [self doesNotRecognizeSelector:selector]; + } +} + +@end + +@implementation NSView(GTMNSAnimatablePropertyContainer) + +- (id)gtm_animatorStopper { + return [[[GTMAnimatorStopper alloc] initWithAnimatablePropertyContainer:self] + autorelease]; +} + +@end + +@implementation NSWindow(GTMNSAnimatablePropertyContainer) + +- (id)gtm_animatorStopper { + return [[[GTMAnimatorStopper alloc] initWithAnimatablePropertyContainer:self] + autorelease]; +} + +@end + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 diff --git a/AppKit/GTMNSAnimatablePropertyContainerTest.h b/AppKit/GTMNSAnimatablePropertyContainerTest.h new file mode 100644 index 0000000..ebfd323 --- /dev/null +++ b/AppKit/GTMNSAnimatablePropertyContainerTest.h @@ -0,0 +1,51 @@ +// +// GTMNSAnimatablePropertyContainerTest.h +// +// Copyright (c) 2010 Google Inc. 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 "GTMSenTestCase.h" +#import + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + +@interface GTMNSAnimatablePropertyContainerWindow : NSWindow +@end + +@interface GTMNSAnimatablePropertyContainerWindowBox : NSBox + +- (void)set:(NSInteger)value; + +@end + +@interface GTMNSAnimatablePropertyContainerWindowController : NSWindowController { + @private + IBOutlet NSBox *nonLayerBox_; + IBOutlet NSBox *layerBox_; +} + +@property (readonly, retain, nonatomic) NSBox *nonLayerBox; +@property (readonly, retain, nonatomic) NSBox *layerBox; + +@end + +@interface GTMNSAnimatablePropertyContainerTest : GTMTestCase { + @private + GTMNSAnimatablePropertyContainerWindowController *windowController_; + BOOL timerCalled_; +} +@end + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 diff --git a/AppKit/GTMNSAnimatablePropertyContainerTest.m b/AppKit/GTMNSAnimatablePropertyContainerTest.m new file mode 100644 index 0000000..0ce89d2 --- /dev/null +++ b/AppKit/GTMNSAnimatablePropertyContainerTest.m @@ -0,0 +1,237 @@ +// +// GTMNSAnimatablePropertyContainerTest.m +// +// Copyright (c) 2010 Google Inc. 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 "GTMNSAnimatablePropertyContainerTest.h" +#import "GTMNSAnimatablePropertyContainer.h" +#import "GTMTypeCasting.h" + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + +@implementation GTMNSAnimatablePropertyContainerWindow + +#if 0 +// Some useful debugging code. Enabled to track animation keys. +- (id)animationForKey:(NSString *)key { + id value = [super animationForKey:key]; + NSLog(@"Value: %@ Key: %@", value, key); + return value; +} +#endif + +@end + +@implementation GTMNSAnimatablePropertyContainerWindowBox + +#if 0 +// Some useful debugging code. Enabled to track animation keys. +- (id)animationForKey:(NSString *)key { + id value = [super animationForKey:key]; + NSLog(@"Value: %@ Key: %@", value, key); + return value; +} +#endif + +- (void)set:(NSInteger)value { + value = value; +} + +@end + +@implementation GTMNSAnimatablePropertyContainerWindowController + +@synthesize nonLayerBox = nonLayerBox_; +@synthesize layerBox = layerBox_; + +- (id)init { + return [super initWithWindowNibName:@"GTMNSAnimatablePropertyContainerTest"]; +} + +- (void)windowWillClose:(NSNotification *)notification { + if (![[notification object] isEqual:[self window]]) { + [[NSException exceptionWithName:SenTestFailureException + reason:@"Bad window in windowWillClose" + userInfo:nil] raise]; + } + [self autorelease]; +} + +@end + +@implementation GTMNSAnimatablePropertyContainerTest + +- (void)setUp { + windowController_ + = [[GTMNSAnimatablePropertyContainerWindowController alloc] init]; + STAssertNotNil(windowController_, nil); + NSWindow *window = [windowController_ window]; + STAssertNotNil(window, nil); +} + +- (void)tearDown { + [windowController_ close]; + windowController_ = nil; +} + +- (void)windowAlphaValueStopper:(NSTimer *)timer { + NSWindow *window = GTM_DYNAMIC_CAST(NSWindow, [timer userInfo]); + timerCalled_ = YES; + [[window gtm_animatorStopper] setAlphaValue:0.25]; + STAssertEquals([window alphaValue], (CGFloat)0.25, nil); +} + +- (void)windowFrameStopper:(NSTimer *)timer { + NSWindow *window = GTM_DYNAMIC_CAST(NSWindow, [timer userInfo]); + timerCalled_ = YES; + [[window gtm_animatorStopper] setFrame:NSMakeRect(300, 300, 150, 150) + display:YES]; + STAssertEquals([window frame], NSMakeRect(300, 300, 150, 150), nil); +} + +- (void)nonLayerFrameStopper:(NSTimer *)timer { + NSView *view = GTM_DYNAMIC_CAST(NSView, [timer userInfo]); + timerCalled_ = YES; + [[view gtm_animatorStopper] setFrame:NSMakeRect(200, 200, 200, 200)]; + STAssertEquals([view frame], NSMakeRect(200, 200, 200, 200), nil); +} + +- (void)layerFrameStopper:(NSTimer *)timer { + NSView *view = GTM_DYNAMIC_CAST(NSView, [timer userInfo]); + timerCalled_ = YES; + [[view gtm_animatorStopper] setFrame:NSMakeRect(200, 200, 200, 200)]; + STAssertEquals([view frame], NSMakeRect(200, 200, 200, 200), nil); +} + +- (void)testWindowAnimations { + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + + // Test Alpha + NSWindow *window = [windowController_ window]; + [window setAlphaValue:1.0]; + timerCalled_ = NO; + [NSAnimationContext beginGrouping]; + NSAnimationContext *currentContext = [NSAnimationContext currentContext]; + [currentContext setDuration:2]; + [[window animator] setAlphaValue:0.5]; + [NSAnimationContext endGrouping]; + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(windowAlphaValueStopper:) + userInfo:window + repeats:NO]; + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; + STAssertTrue(timerCalled_, nil); + STAssertEquals([window alphaValue], (CGFloat)0.25, nil); + + // Test Frame + [window setFrame:NSMakeRect(100, 100, 100, 100) display:YES]; + timerCalled_ = NO; + [NSAnimationContext beginGrouping]; + currentContext = [NSAnimationContext currentContext]; + [currentContext setDuration:2]; + [[window animator] setFrame:NSMakeRect(200, 200, 200, 200) display:YES]; + [NSAnimationContext endGrouping]; + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(windowFrameStopper:) + userInfo:window + repeats:NO]; + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; + STAssertTrue(timerCalled_, nil); + STAssertEquals([window frame], NSMakeRect(300, 300, 150, 150), nil); + + // Test non-animation value + [window setTitle:@"Foo"]; + [[window gtm_animatorStopper] setTitle:@"Bar"]; + STAssertEquals([window title], @"Bar", nil); + + // Test bad selector + STAssertThrows([[window gtm_animatorStopper] testWindowAnimations], nil); +} + +- (void)testNonLayerViewAnimations { + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + + NSBox *nonLayerBox = [windowController_ nonLayerBox]; + STAssertNotNil(nonLayerBox, nil); + + // Test frame + [nonLayerBox setFrame:NSMakeRect(50, 50, 50, 50)]; + timerCalled_ = NO; + [NSAnimationContext beginGrouping]; + NSAnimationContext *currentContext = [NSAnimationContext currentContext]; + [currentContext setDuration:2]; + [[nonLayerBox animator] setFrame:NSMakeRect(100, 100, 100, 100)]; + [NSAnimationContext endGrouping]; + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(nonLayerFrameStopper:) + userInfo:nonLayerBox + repeats:NO]; + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; + STAssertTrue(timerCalled_, nil); + STAssertEquals([nonLayerBox frame], NSMakeRect(200, 200, 200, 200), nil); + + // Test non-animation value + [nonLayerBox setToolTip:@"Foo"]; + [[nonLayerBox gtm_animatorStopper] setToolTip:@"Bar"]; + STAssertEquals([nonLayerBox toolTip], @"Bar", nil); + + // Test bad selector + STAssertThrows([[nonLayerBox gtm_animatorStopper] testNonLayerViewAnimations], + nil); +} + +- (void)testLayerViewAnimations { + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + + NSBox *layerBox = [windowController_ layerBox]; + STAssertNotNil(layerBox, nil); + + // Test frame + [layerBox setFrame:NSMakeRect(50, 50, 50, 50)]; + timerCalled_ = NO; + [NSAnimationContext beginGrouping]; + NSAnimationContext *currentContext = [NSAnimationContext currentContext]; + [currentContext setDuration:2]; + [[layerBox animator] setFrame:NSMakeRect(100, 100, 100, 100)]; + [NSAnimationContext endGrouping]; + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(layerFrameStopper:) + userInfo:layerBox + repeats:NO]; + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; + STAssertTrue(timerCalled_, nil); + STAssertEquals([layerBox frame], NSMakeRect(200, 200, 200, 200), nil); + + // Test non-animation value + [layerBox setToolTip:@"Foo"]; + [[layerBox gtm_animatorStopper] setToolTip:@"Bar"]; + STAssertEquals([layerBox toolTip], @"Bar", nil); + + // Test bad selector + STAssertThrows([[layerBox gtm_animatorStopper] testLayerViewAnimations], + nil); + + // Test Short Selector + STAssertThrows([[layerBox gtm_animatorStopper] set:1], nil); +} + +@end + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 diff --git a/AppKit/GTMNSAnimatablePropertyContainerTest.xib b/AppKit/GTMNSAnimatablePropertyContainerTest.xib new file mode 100644 index 0000000..3065ab5 --- /dev/null +++ b/AppKit/GTMNSAnimatablePropertyContainerTest.xib @@ -0,0 +1,426 @@ + + + + 1050 + 9L31a + 680 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilderKit + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + YES + + + + YES + + GTMNSAnimatablePropertyContainerWindowController + + + FirstResponder + + + NSApplication + + + 15 + 2 + {{196, 240}, {480, 270}} + 536870912 + Window + GTMNSAnimatablePropertyContainerWindow + + {3.40282e+38, 3.40282e+38} + + + 256 + + YES + + + 12 + + YES + + + 256 + {{1, 1}, {120, 113}} + + + + {{36, 135}, {122, 115}} + + {0, 0} + + 67239424 + 0 + NonLayerBox + + LucidaGrande + 1.100000e+01 + 3100 + + + 6 + System + textBackgroundColor + + 3 + MQA + + + + 3 + MCAwLjgwMDAwMDAxAA + + + + 1 + 4 + 2 + NO + + 1 + MCAwIDEAA + + + + + 12 + + YES + + + 256 + {{1, 1}, {120, 113}} + + + + {{238, 135}, {122, 115}} + + YES + {0, 0} + + 67239424 + 0 + LayerBox + + + + 3 + MCAwLjgwMDAwMDAxAA + + + + 1 + 4 + 2 + NO + + 1 + MSAwIDEAA + + + + {480, 270} + + + {{0, 0}, {1680, 1028}} + {3.40282e+38, 3.40282e+38} + + + + + YES + + + nonLayerBox_ + + + + 6 + + + + layerBox_ + + + + 7 + + + + delegate + + + + 8 + + + + window + + + + 9 + + + + + YES + + 0 + + YES + + + + + + -2 + + + RmlsZSdzIE93bmVyA + + + -1 + + + First Responder + + + -3 + + + Application + + + 1 + + + YES + + + + + + 2 + + + YES + + + + + + + 3 + + + + + 4 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 1.IBWindowTemplateEditedContentRect + 1.NSWindowTemplate.visibleAtLaunch + 1.WindowOrigin + 1.editorWindowContentRectSynchronizationRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + 4.CustomClassName + 4.IBPluginDependency + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilderKit + com.apple.InterfaceBuilderKit + {{152, 488}, {480, 270}} + com.apple.InterfaceBuilder.CocoaPlugin + {{152, 488}, {480, 270}} + + {196, 240} + {{202, 428}, {480, 270}} + com.apple.InterfaceBuilder.CocoaPlugin + GTMNSAnimatablePropertyContainerWindowBox + com.apple.InterfaceBuilder.CocoaPlugin + GTMNSAnimatablePropertyContainerWindowBox + com.apple.InterfaceBuilder.CocoaPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 9 + + + + YES + + GTMNSAnimatablePropertyContainerWindow + NSWindow + + IBProjectSource + AppKit/GTMNSAnimatablePropertyContainerTest.h + + + + GTMNSAnimatablePropertyContainerWindowBox + NSBox + + + + GTMNSAnimatablePropertyContainerWindowController + NSWindowController + + YES + + YES + layerBox_ + nonLayerBox_ + + + YES + NSBox + NSBox + + + + + + NSApplication + + IBProjectSource + UnitTesting/GTMAppKit+UnitTesting.h + + + + NSMenu + + + + NSObject + + IBProjectSource + AppKit/GTMCarbonEvent.h + + + + NSObject + + IBProjectSource + AppKit/GTMDelegatingTableColumn.h + + + + NSObject + + IBProjectSource + Foundation/GTMHTTPServer.h + + + + NSObject + + IBProjectSource + Foundation/GTMNSAppleEventDescriptor+Foundation.h + + + + NSObject + + IBProjectSource + Foundation/GTMNSObject+KeyValueObserving.h + + + + NSObject + + IBProjectSource + UnitTesting/GTMCALayer+UnitTesting.h + + + + NSObject + + IBProjectSource + UnitTesting/GTMNSObject+BindingUnitTesting.h + + + + NSObject + + IBProjectSource + UnitTesting/GTMNSObject+UnitTesting.h + + + + NSView + + IBProjectSource + AppKit/GTMNSAnimatablePropertyContainer.h + + + + NSView + + + + NSWindow + + + + NSWindow + + + + + 0 + ../GTM.xcodeproj + 3 + + -- cgit v1.2.3