a433398d43
It's pretty fun to play with sliding tabs…. :D
152 lines
4.8 KiB
Objective-C
152 lines
4.8 KiB
Objective-C
/*
|
|
Copyright 2011 Twitter, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this work except in compliance with the License.
|
|
You may obtain a copy of the License in the LICENSE file, or 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 "ExampleTabBar.h"
|
|
|
|
@interface ExampleTab : TUIControl
|
|
|
|
@property (nonatomic, assign) CGFloat originalPosition;
|
|
|
|
@end
|
|
|
|
@implementation ExampleTab
|
|
|
|
// Convinience to call delegate methods, since by default, our
|
|
// superview SHOULD be a tab bar, and nothing else. This will
|
|
// horribly crash if you add an ExampleTab to anything but a tab bar.
|
|
- (ExampleTabBar *)tabBar {
|
|
return (ExampleTabBar *)self.superview;
|
|
}
|
|
|
|
// If a tracking event occurs within a tab, we want to move the
|
|
// tab around, so return YES.
|
|
- (BOOL)beginTrackingWithEvent:(NSEvent *)event {
|
|
[self setNeedsDisplay];
|
|
|
|
// So the tab doesn't move over just on mouse pressed.
|
|
self.originalPosition = [self convertPoint:[event locationInWindow] fromView:nil].x;
|
|
|
|
return YES;
|
|
}
|
|
|
|
// Find the tab that was being dragged- although this isn't the
|
|
// most efficient way, this will suffice for now.
|
|
- (BOOL)continueTrackingWithEvent:(NSEvent *)event {
|
|
|
|
// Offset the tab's x origin by whatever we dragged by.
|
|
// Animating it makes it fun if the drag goes pretty far.
|
|
CGFloat currentPosition = [self convertPoint:[event locationInWindow] fromView:nil].x;
|
|
[TUIView animateWithDuration:0.1 animations:^{
|
|
|
|
// Setting an ease-in-out animation curve slows down
|
|
// the animation at the start and finish, but speeds it up
|
|
// during the middle. Just for that "don't slide me!" fun.
|
|
[TUIView setAnimationCurve:TUIViewAnimationCurveEaseInOut];
|
|
|
|
CGRect draggedRect = self.frame;
|
|
draggedRect.origin.x += roundf(currentPosition - self.originalPosition);
|
|
self.frame = draggedRect;
|
|
}];
|
|
|
|
return YES;
|
|
}
|
|
|
|
// Restore tabs to their original condition.
|
|
- (void)endTrackingWithEvent:(NSEvent *)event {
|
|
|
|
// By nature, the event *must* be inside the tab's bounds, otherwise the
|
|
// tracking process will not be invoked. If we were dragged, we were NOT
|
|
// pressed, so don't call the delegate.
|
|
CGFloat currentPosition = [self convertPoint:[event locationInWindow] fromView:nil].x;
|
|
if(self.originalPosition == currentPosition)
|
|
[[self tabBar].delegate tabBar:[self tabBar] didSelectTab:self.tag];
|
|
|
|
// Since tracking is done, move the tab back. This whole ordeal lets us
|
|
// "stretch" our tabs around.
|
|
float originalPoint = self.tag * (self.tabBar.bounds.size.width / self.tabBar.tabViews.count);
|
|
[TUIView animateWithDuration:0.2 animations:^{
|
|
CGRect draggedRect = self.frame;
|
|
draggedRect.origin.x = roundf(originalPoint);
|
|
self.frame = draggedRect;
|
|
}];
|
|
|
|
// Rather than a simple -setNeedsDisplay, let's fade it back out.
|
|
[TUIView animateWithDuration:0.3 animations:^{
|
|
|
|
// -redraw forces a .contents update immediately based on drawRect,
|
|
// and it happens inside an animation block, so CoreAnimation gives
|
|
// us a cross-fade for free.
|
|
[self redraw];
|
|
}];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface ExampleTabBar ()
|
|
|
|
@property (nonatomic, assign) ExampleTab *draggingTab;
|
|
|
|
@end
|
|
|
|
@implementation ExampleTabBar
|
|
|
|
@synthesize delegate;
|
|
@synthesize tabViews;
|
|
|
|
- (id)initWithNumberOfTabs:(NSInteger)nTabs
|
|
{
|
|
if((self = [super initWithFrame:CGRectZero])) {
|
|
NSMutableArray *_tabViews = [NSMutableArray arrayWithCapacity:nTabs];
|
|
for(int i = 0; i < nTabs; ++i) {
|
|
ExampleTab *t = [[ExampleTab alloc] initWithFrame:CGRectZero];
|
|
t.tag = i;
|
|
t.layout = ^(TUIView *v) { // the layout of an individual tab is a function of the superview bounds, the number of tabs, and the current tab index
|
|
CGRect b = v.superview.bounds; // reference the passed-in 'v' rather than 't' to avoid a retain cycle
|
|
float width = (b.size.width / nTabs);
|
|
float x = i * width;
|
|
return CGRectMake(roundf(x), 0, roundf(width), b.size.height);
|
|
};
|
|
[self addSubview:t];
|
|
[_tabViews addObject:t];
|
|
}
|
|
|
|
tabViews = [[NSArray alloc] initWithArray:_tabViews];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void)drawRect:(CGRect)rect
|
|
{
|
|
// draw tab bar background
|
|
|
|
CGRect b = self.bounds;
|
|
CGContextRef ctx = TUIGraphicsGetCurrentContext();
|
|
|
|
// gray gradient
|
|
CGFloat colorA[] = { 0.85, 0.85, 0.85, 1.0 };
|
|
CGFloat colorB[] = { 0.71, 0.71, 0.71, 1.0 };
|
|
CGContextDrawLinearGradientBetweenPoints(ctx, CGPointMake(0, b.size.height), colorA, CGPointMake(0, 0), colorB);
|
|
|
|
// top emboss
|
|
CGContextSetRGBFillColor(ctx, 1, 1, 1, 0.5);
|
|
CGContextFillRect(ctx, CGRectMake(0, b.size.height-2, b.size.width, 1));
|
|
CGContextSetRGBFillColor(ctx, 0, 0, 0, 0.3);
|
|
CGContextFillRect(ctx, CGRectMake(0, b.size.height-1, b.size.width, 1));
|
|
}
|
|
|
|
@end
|