600 lines
23 KiB
Objective-C
600 lines
23 KiB
Objective-C
/*
|
|
Copyright 2012 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 "TUIPopover.h"
|
|
#import "CAAnimation+TUIExtensions.h"
|
|
#import "NSColor+TUIExtensions.h"
|
|
#import "TUICGAdditions.h"
|
|
#import "TUINSView.h"
|
|
#import "TUINSWindow.h"
|
|
#import "TUIViewController.h"
|
|
|
|
//***************************************************************************
|
|
|
|
@interface TUIPopoverBackgroundView ()
|
|
|
|
@property (nonatomic, unsafe_unretained) CGRect screenOriginRect;
|
|
@property (nonatomic, unsafe_unretained) CGRectEdge popoverEdge;
|
|
|
|
- (CGRectEdge)arrowEdgeForPopoverEdge:(CGRectEdge)popoverEdge;
|
|
- (void)updateMaskLayer;
|
|
|
|
@end
|
|
|
|
//***************************************************************************
|
|
|
|
@interface TUIPopoverWindowContentView : NSView
|
|
|
|
@property (nonatomic, readonly) TUINSView *nsView;
|
|
@property (nonatomic, unsafe_unretained) CGRectEdge arrowEdge;
|
|
|
|
@end
|
|
|
|
//***************************************************************************
|
|
|
|
NSTimeInterval const TUIPopoverDefaultFadeoutDuration = 0.3;
|
|
|
|
//***************************************************************************
|
|
|
|
@interface TUIPopover ()
|
|
|
|
@property (nonatomic, strong) TUINSWindow *popoverWindow;
|
|
@property (nonatomic, unsafe_unretained) id transientEventMonitor;
|
|
@property (nonatomic, unsafe_unretained) BOOL animating;
|
|
@property (nonatomic, assign) CGSize originalViewSize;
|
|
|
|
- (void)removeEventMonitor;
|
|
|
|
@end
|
|
|
|
//***************************************************************************
|
|
|
|
@implementation TUIPopover
|
|
|
|
@synthesize contentViewController = _contentViewController;
|
|
@synthesize backgroundViewClass = _backgroundViewClass;
|
|
@synthesize contentSize = _contentSize;
|
|
@synthesize animates = _animates;
|
|
@synthesize behaviour = _behaviour;
|
|
@synthesize positioningRect = _positioningRect;
|
|
@synthesize willCloseBlock = _willCloseBlock;
|
|
@synthesize didCloseBlock = _didCloseBlock;
|
|
@synthesize willShowBlock = _willShowBlock;
|
|
@synthesize didShowBlock = _didShowBlock;
|
|
|
|
@synthesize popoverWindow = _popoverWindow;
|
|
@synthesize transientEventMonitor = _transientEventMonitor;
|
|
@synthesize animating = _animating;
|
|
|
|
@synthesize originalViewSize = _originalViewSize;
|
|
|
|
- (id)initWithContentViewController:(TUIViewController *)viewController
|
|
{
|
|
self = [super init];
|
|
if (self == nil)
|
|
return nil;
|
|
|
|
_contentViewController = viewController;
|
|
_backgroundViewClass = [TUIPopoverBackgroundView class];
|
|
_behaviour = TUIPopoverViewControllerBehaviourApplicationDefined;
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Derived Properties
|
|
|
|
- (BOOL)shown
|
|
{
|
|
return (self.popoverWindow.contentView != nil);
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Showing
|
|
|
|
- (void)showRelativeToRect:(CGRect)positioningRect ofView:(TUIView *)positioningView preferredEdge:(CGRectEdge)preferredEdge
|
|
{
|
|
if (self.shown)
|
|
return;
|
|
|
|
if (self.willShowBlock != nil)
|
|
self.willShowBlock(self);
|
|
|
|
if (self.behaviour != TUIPopoverViewControllerBehaviourApplicationDefined) {
|
|
if (self.transientEventMonitor != nil) {
|
|
[self removeEventMonitor];
|
|
}
|
|
|
|
self.transientEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:(NSLeftMouseDownMask | NSRightMouseDownMask | NSKeyUpMask) handler: ^ (NSEvent *event) {
|
|
if (self.popoverWindow == nil)
|
|
return event;
|
|
|
|
static NSUInteger escapeKey = 53;
|
|
BOOL shouldClose = (event.type == NSLeftMouseDown || event.type == NSRightMouseDown ? (!NSPointInRect([NSEvent mouseLocation], self.popoverWindow.frame) && self.behaviour == TUIPopoverViewControllerBehaviourTransient) : event.keyCode == escapeKey);
|
|
|
|
if (shouldClose) {
|
|
[self close];
|
|
}
|
|
|
|
return event;
|
|
}];
|
|
}
|
|
|
|
if (CGRectEqualToRect(positioningRect, CGRectZero))
|
|
positioningRect = [positioningView bounds];
|
|
|
|
CGRect basePositioningRect = [positioningView convertRect:positioningRect toView:nil];
|
|
NSRect windowRelativeRect = [positioningView.nsView convertRect:basePositioningRect toView:nil];
|
|
CGRect screenPositioningRect = windowRelativeRect;
|
|
screenPositioningRect.origin = [positioningView.nsWindow convertBaseToScreen:windowRelativeRect.origin];
|
|
self.originalViewSize = self.contentViewController.view.frame.size;
|
|
CGSize contentViewSize = (CGSizeEqualToSize(self.contentSize, CGSizeZero) ? self.contentViewController.view.frame.size : self.contentSize);
|
|
|
|
CGRect (^popoverRectForEdge)(CGRectEdge) = ^ (CGRectEdge popoverEdge)
|
|
{
|
|
CGSize popoverSize = [self.backgroundViewClass sizeForBackgroundViewWithContentSize:contentViewSize popoverEdge:popoverEdge];
|
|
CGRect returnRect = NSMakeRect(0.0, 0.0, popoverSize.width, popoverSize.height);
|
|
if (popoverEdge == CGRectMinYEdge) {
|
|
CGFloat xOrigin = NSMidX(screenPositioningRect) - floor(popoverSize.width / 2.0);
|
|
CGFloat yOrigin = NSMinY(screenPositioningRect) - popoverSize.height;
|
|
returnRect.origin = NSMakePoint(xOrigin, yOrigin);
|
|
} else if (popoverEdge == CGRectMaxYEdge) {
|
|
CGFloat xOrigin = NSMidX(screenPositioningRect) - floor(popoverSize.width / 2.0);
|
|
returnRect.origin = NSMakePoint(xOrigin, NSMaxY(screenPositioningRect));
|
|
} else if (popoverEdge == CGRectMinXEdge) {
|
|
CGFloat xOrigin = NSMinX(screenPositioningRect) - popoverSize.width;
|
|
CGFloat yOrigin = NSMidY(screenPositioningRect) - floor(popoverSize.height / 2.0);
|
|
returnRect.origin = NSMakePoint(xOrigin, yOrigin);
|
|
} else if (popoverEdge == CGRectMaxXEdge) {
|
|
CGFloat yOrigin = NSMidY(screenPositioningRect) - floor(popoverSize.height / 2.0);
|
|
returnRect.origin = NSMakePoint(NSMaxX(screenPositioningRect), yOrigin);
|
|
} else {
|
|
returnRect = CGRectZero;
|
|
}
|
|
|
|
return returnRect;
|
|
};
|
|
|
|
BOOL (^checkPopoverSizeForScreenWithPopoverEdge)(CGRectEdge) = ^ (CGRectEdge popoverEdge)
|
|
{
|
|
CGRect popoverRect = popoverRectForEdge(popoverEdge);
|
|
return NSContainsRect(positioningView.nsWindow.screen.visibleFrame, popoverRect);
|
|
};
|
|
|
|
//This is as ugly as sin… but it gets the job done. I couldn't think of a nice way to code this but still get the desired behaviour
|
|
__block CGRectEdge popoverEdge = preferredEdge;
|
|
CGRect (^popoverRect)() = ^
|
|
{
|
|
CGRectEdge (^nextEdgeForEdge)(CGRectEdge) = ^ (CGRectEdge currentEdge)
|
|
{
|
|
if (currentEdge == CGRectMaxXEdge) {
|
|
return (CGRectEdge)(preferredEdge == CGRectMinXEdge ? CGRectMaxYEdge : CGRectMinXEdge);
|
|
} else if (currentEdge == CGRectMinXEdge) {
|
|
return (CGRectEdge)(preferredEdge == CGRectMaxXEdge ? CGRectMaxYEdge : CGRectMaxXEdge);
|
|
} else if (currentEdge == CGRectMaxYEdge) {
|
|
return (CGRectEdge)(preferredEdge == CGRectMinYEdge ? CGRectMaxXEdge : CGRectMinYEdge);
|
|
} else if (currentEdge == CGRectMinYEdge) {
|
|
return (CGRectEdge)(preferredEdge == CGRectMaxYEdge ? CGRectMaxXEdge : CGRectMaxYEdge);
|
|
}
|
|
|
|
return currentEdge;
|
|
};
|
|
|
|
CGRect (^fitRectToScreen)(CGRect) = ^ (CGRect proposedRect) {
|
|
NSRect screenRect = positioningView.nsWindow.screen.visibleFrame;
|
|
|
|
if (proposedRect.origin.y < NSMinY(screenRect))
|
|
proposedRect.origin.y = NSMinY(screenRect);
|
|
if (proposedRect.origin.x < NSMinX(screenRect))
|
|
proposedRect.origin.x = NSMinX(screenRect);
|
|
|
|
if (NSMaxY(proposedRect) > NSMaxY(screenRect))
|
|
proposedRect.origin.y = (NSMaxY(screenRect) - NSHeight(proposedRect));
|
|
if (NSMaxX(proposedRect) > NSMaxX(screenRect))
|
|
proposedRect.origin.x = (NSMaxX(screenRect) - NSWidth(proposedRect));
|
|
|
|
return proposedRect;
|
|
};
|
|
|
|
NSUInteger attemptCount = 0;
|
|
while (!checkPopoverSizeForScreenWithPopoverEdge(popoverEdge)) {
|
|
if (attemptCount > 4) {
|
|
popoverEdge = preferredEdge;
|
|
return fitRectToScreen(popoverRectForEdge(popoverEdge));
|
|
break;
|
|
}
|
|
|
|
popoverEdge = nextEdgeForEdge(popoverEdge);
|
|
attemptCount ++;
|
|
}
|
|
|
|
return (CGRect)popoverRectForEdge(popoverEdge);
|
|
};
|
|
|
|
CGRect popoverScreenRect = popoverRect();
|
|
TUIPopoverBackgroundView *backgroundView = [self.backgroundViewClass backgroundViewForContentSize:contentViewSize popoverEdge:popoverEdge originScreenRect:screenPositioningRect];
|
|
|
|
CGRect contentViewFrame = [self.backgroundViewClass contentViewFrameForBackgroundFrame:backgroundView.bounds popoverEdge:popoverEdge];
|
|
self.contentViewController.view.frame = contentViewFrame;
|
|
[backgroundView addSubview:self.contentViewController.view];
|
|
self.popoverWindow = [[TUINSWindow alloc] initWithContentRect:popoverScreenRect];
|
|
[self.popoverWindow setReleasedWhenClosed:NO];
|
|
TUIPopoverWindowContentView *contentView = [[TUIPopoverWindowContentView alloc] initWithFrame:backgroundView.bounds];
|
|
contentView.arrowEdge = [backgroundView arrowEdgeForPopoverEdge:popoverEdge];
|
|
contentView.nsView.rootView = backgroundView;
|
|
[self.popoverWindow setOpaque:NO];
|
|
[self.popoverWindow setBackgroundColor:[NSColor clearColor]];
|
|
self.popoverWindow.contentView = contentView;
|
|
self.popoverWindow.alphaValue = 0.0;
|
|
[positioningView.nsWindow addChildWindow:self.popoverWindow ordered:NSWindowAbove];
|
|
[self.popoverWindow makeKeyAndOrderFront:self];
|
|
[backgroundView updateMaskLayer];
|
|
|
|
[self.contentViewController viewWillAppear:YES];
|
|
|
|
CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"alphaValue"];
|
|
fadeInAnimation.duration = 0.3;
|
|
fadeInAnimation.tui_completionBlock = ^ {
|
|
self.animating = NO;
|
|
[self.contentViewController viewDidAppear:YES];
|
|
|
|
if (self.didShowBlock)
|
|
self.didShowBlock(self);
|
|
};
|
|
|
|
self.popoverWindow.animations = [NSDictionary dictionaryWithObject:fadeInAnimation forKey:@"alphaValue"];
|
|
self.animating = YES;
|
|
[self.popoverWindow.animator setAlphaValue:1.0];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Closing
|
|
|
|
- (void)close
|
|
{
|
|
[self closeWithFadeoutDuration:TUIPopoverDefaultFadeoutDuration];
|
|
}
|
|
|
|
- (void)closeWithFadeoutDuration:(NSTimeInterval)duration
|
|
{
|
|
if (self.animating)
|
|
return;
|
|
|
|
if (self.transientEventMonitor != nil) {
|
|
[self removeEventMonitor];
|
|
}
|
|
|
|
if (self.willCloseBlock != nil)
|
|
self.willCloseBlock(self);
|
|
|
|
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"alphaValue"];
|
|
fadeOutAnimation.duration = duration;
|
|
fadeOutAnimation.tui_completionBlock = ^ {
|
|
[self.popoverWindow.parentWindow removeChildWindow:self.popoverWindow];
|
|
[self.popoverWindow close];
|
|
self.popoverWindow.contentView = nil;
|
|
self.animating = NO;
|
|
|
|
if (self.didCloseBlock != nil)
|
|
self.didCloseBlock(self);
|
|
|
|
self.contentViewController.view.frame = CGRectMake(self.contentViewController.view.frame.origin.x, self.contentViewController.view.frame.origin.y, self.originalViewSize.width, self.originalViewSize.height);
|
|
};
|
|
|
|
self.popoverWindow.animations = [NSDictionary dictionaryWithObject:fadeOutAnimation forKey:@"alphaValue"];
|
|
self.animating = YES;
|
|
[self.popoverWindow.animator setAlphaValue:0.0];
|
|
}
|
|
|
|
- (IBAction)performClose:(id)sender
|
|
{
|
|
[self close];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Event Monitor
|
|
|
|
- (void)removeEventMonitor
|
|
{
|
|
[NSEvent removeMonitor:self.transientEventMonitor];
|
|
self.transientEventMonitor = nil;
|
|
}
|
|
|
|
@end
|
|
|
|
//***************************************************************************
|
|
|
|
CGFloat const TUIPopoverBackgroundViewBorderRadius = 5.0;
|
|
CGFloat const TUIPopoverBackgroundViewArrowHeight = 17.0;
|
|
CGFloat const TUIPopoverBackgroundViewArrowWidth = 35.0;
|
|
|
|
//***************************************************************************
|
|
|
|
@implementation TUIPopoverBackgroundView
|
|
|
|
@synthesize fillColor = _fillColor;
|
|
@synthesize strokeColor = _strokeColor;
|
|
|
|
@synthesize screenOriginRect = _screenOriginRect;
|
|
@synthesize popoverEdge = _popoverEdge;
|
|
|
|
+ (CGSize)sizeForBackgroundViewWithContentSize:(CGSize)contentSize popoverEdge:(CGRectEdge)popoverEdge
|
|
{
|
|
CGSize returnSize = contentSize;
|
|
if (popoverEdge == CGRectMaxXEdge || popoverEdge == CGRectMinXEdge) {
|
|
returnSize.width += TUIPopoverBackgroundViewArrowHeight;
|
|
} else {
|
|
returnSize.height += TUIPopoverBackgroundViewArrowHeight;
|
|
}
|
|
|
|
returnSize.width ++;
|
|
returnSize.height ++;
|
|
|
|
return returnSize;
|
|
}
|
|
|
|
+ (CGRect)contentViewFrameForBackgroundFrame:(CGRect)backgroundFrame popoverEdge:(CGRectEdge)popoverEdge
|
|
{
|
|
CGRect returnFrame = NSInsetRect(backgroundFrame, 1.0, 1.0);
|
|
switch (popoverEdge) {
|
|
case CGRectMinXEdge:
|
|
returnFrame.size.width -= TUIPopoverBackgroundViewArrowHeight;
|
|
break;
|
|
case CGRectMinYEdge:
|
|
returnFrame.size.height -= TUIPopoverBackgroundViewArrowHeight;
|
|
break;
|
|
case CGRectMaxXEdge:
|
|
returnFrame.size.width -= TUIPopoverBackgroundViewArrowHeight;
|
|
returnFrame.origin.x += TUIPopoverBackgroundViewArrowHeight;
|
|
break;
|
|
case CGRectMaxYEdge:
|
|
returnFrame.size.height -= TUIPopoverBackgroundViewArrowHeight;
|
|
returnFrame.origin.y += TUIPopoverBackgroundViewArrowHeight;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return returnFrame;
|
|
}
|
|
|
|
+ (TUIPopoverBackgroundView *)backgroundViewForContentSize:(CGSize)contentSize popoverEdge:(CGRectEdge)popoverEdge originScreenRect:(CGRect)originScreenRect
|
|
{
|
|
CGSize size = [self sizeForBackgroundViewWithContentSize:contentSize popoverEdge:popoverEdge];
|
|
TUIPopoverBackgroundView *returnView = [[self.class alloc] initWithFrame:NSMakeRect(0.0, 0.0, size.width, size.height) popoverEdge:popoverEdge originScreenRect:originScreenRect];
|
|
return returnView;
|
|
}
|
|
|
|
- (CGPathRef)newPopoverPathForEdge:(CGRectEdge)popoverEdge inFrame:(CGRect)frame
|
|
{
|
|
CGRectEdge arrowEdge = [self arrowEdgeForPopoverEdge:popoverEdge];
|
|
|
|
CGRect contentRect = CGRectIntegral([[self class] contentViewFrameForBackgroundFrame:frame popoverEdge:self.popoverEdge]);
|
|
CGFloat minX = NSMinX(contentRect);
|
|
CGFloat maxX = NSMaxX(contentRect);
|
|
CGFloat minY = NSMinY(contentRect);
|
|
CGFloat maxY = NSMaxY(contentRect);
|
|
|
|
CGRect windowRect = self.screenOriginRect;
|
|
windowRect.origin = [self.nsWindow convertScreenToBase:self.screenOriginRect.origin];
|
|
CGRect originRect = [self convertRect:windowRect fromView:nil]; //hmm as we have no superview at this point is this retarded?
|
|
CGFloat midOriginY = floor(NSMidY(originRect));
|
|
CGFloat midOriginX = floor(NSMidX(originRect));
|
|
|
|
CGFloat maxArrowX = 0.0;
|
|
CGFloat minArrowX = 0.0;
|
|
CGFloat minArrowY = 0.0;
|
|
CGFloat maxArrowY = 0.0;
|
|
|
|
// Even I have no idea at this point… :trollface:
|
|
// So we don't have a weird arrow situation we need to make sure we draw it within the radius.
|
|
// If we have to nudge it then we have to shrink the arrow as otherwise it looks all wonky and weird.
|
|
// That is what this complete mess below does.
|
|
|
|
if (arrowEdge == CGRectMinYEdge || arrowEdge == CGRectMaxYEdge) {
|
|
maxArrowX = floor(midOriginX + (TUIPopoverBackgroundViewArrowWidth / 2.0));
|
|
CGFloat maxPossible = (NSMaxX(contentRect) - TUIPopoverBackgroundViewBorderRadius);
|
|
if (maxArrowX > maxPossible) {
|
|
CGFloat delta = maxArrowX - maxPossible;
|
|
maxArrowX = maxPossible;
|
|
minArrowX = maxArrowX - (TUIPopoverBackgroundViewArrowWidth - delta);
|
|
} else {
|
|
minArrowX = floor(midOriginX - (TUIPopoverBackgroundViewArrowWidth / 2.0));
|
|
if (minArrowX < TUIPopoverBackgroundViewBorderRadius) {
|
|
CGFloat delta = TUIPopoverBackgroundViewBorderRadius - minArrowX;
|
|
minArrowX = TUIPopoverBackgroundViewBorderRadius;
|
|
maxArrowX = minArrowX + (TUIPopoverBackgroundViewArrowWidth - (delta * 2));
|
|
}
|
|
}
|
|
} else {
|
|
minArrowY = floor(midOriginY - (TUIPopoverBackgroundViewArrowWidth / 2.0));
|
|
if (minArrowY < TUIPopoverBackgroundViewBorderRadius) {
|
|
CGFloat delta = TUIPopoverBackgroundViewBorderRadius - minArrowY;
|
|
minArrowY = TUIPopoverBackgroundViewBorderRadius;
|
|
maxArrowY = minArrowY + (TUIPopoverBackgroundViewArrowWidth - (delta * 2));
|
|
} else {
|
|
maxArrowY = floor(midOriginY + (TUIPopoverBackgroundViewArrowWidth / 2.0));
|
|
CGFloat maxPossible = (NSMaxY(contentRect) - TUIPopoverBackgroundViewBorderRadius);
|
|
if (maxArrowY > maxPossible) {
|
|
CGFloat delta = maxArrowY - maxPossible;
|
|
maxArrowY = maxPossible;
|
|
minArrowY = maxArrowY - (TUIPopoverBackgroundViewArrowWidth - delta);
|
|
}
|
|
}
|
|
}
|
|
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
CGPathMoveToPoint(path, NULL, minX, floor(minY + TUIPopoverBackgroundViewBorderRadius));
|
|
if (arrowEdge == CGRectMinXEdge) {
|
|
CGPathAddLineToPoint(path, NULL, minX, minArrowY);
|
|
CGPathAddLineToPoint(path, NULL, floor(minX - TUIPopoverBackgroundViewArrowHeight), midOriginY);
|
|
CGPathAddLineToPoint(path, NULL, minX, maxArrowY);
|
|
}
|
|
|
|
CGPathAddArc(path, NULL, floor(minX + TUIPopoverBackgroundViewBorderRadius), floor(minY + contentRect.size.height - TUIPopoverBackgroundViewBorderRadius), TUIPopoverBackgroundViewBorderRadius, M_PI, M_PI / 2, 1);
|
|
if (arrowEdge == CGRectMaxYEdge) {
|
|
CGPathAddLineToPoint(path, NULL, minArrowX, maxY);
|
|
CGPathAddLineToPoint(path, NULL, midOriginX, floor(maxY + TUIPopoverBackgroundViewArrowHeight));
|
|
CGPathAddLineToPoint(path, NULL, maxArrowX, maxY);
|
|
}
|
|
|
|
CGPathAddArc(path, NULL, floor(minX + contentRect.size.width - TUIPopoverBackgroundViewBorderRadius), floor(minY + contentRect.size.height - TUIPopoverBackgroundViewBorderRadius), TUIPopoverBackgroundViewBorderRadius, M_PI / 2, 0.0, 1);
|
|
if (arrowEdge == CGRectMaxXEdge) {
|
|
CGPathAddLineToPoint(path, NULL, maxX, maxArrowY);
|
|
CGPathAddLineToPoint(path, NULL, floor(maxX + TUIPopoverBackgroundViewArrowHeight), midOriginY);
|
|
CGPathAddLineToPoint(path, NULL, maxX, minArrowY);
|
|
}
|
|
|
|
CGPathAddArc(path, NULL, floor(contentRect.origin.x + contentRect.size.width - TUIPopoverBackgroundViewBorderRadius), floor(minY + TUIPopoverBackgroundViewBorderRadius), TUIPopoverBackgroundViewBorderRadius, 0.0, -M_PI / 2, 1);
|
|
if (arrowEdge == CGRectMinYEdge) {
|
|
CGPathAddLineToPoint(path, NULL, maxArrowX, minY);
|
|
CGPathAddLineToPoint(path, NULL, midOriginX, floor(minY - TUIPopoverBackgroundViewArrowHeight));
|
|
CGPathAddLineToPoint(path, NULL, minArrowX, minY);
|
|
}
|
|
|
|
CGPathAddArc(path, NULL, floor(minX + TUIPopoverBackgroundViewBorderRadius), floor(minY + TUIPopoverBackgroundViewBorderRadius), TUIPopoverBackgroundViewBorderRadius, -M_PI / 2, M_PI, 1);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
- (id)initWithFrame:(CGRect)frame popoverEdge:(CGRectEdge)popoverEdge originScreenRect:(CGRect)originScreenRect //originScreenRect is in the screen coordinate space
|
|
{
|
|
self = [super initWithFrame:frame];
|
|
if (self == nil)
|
|
return nil;
|
|
|
|
_popoverEdge = popoverEdge;
|
|
_screenOriginRect = originScreenRect;
|
|
_strokeColor = [NSColor blackColor];
|
|
_fillColor = [NSColor whiteColor];
|
|
|
|
__block __unsafe_unretained TUIPopoverBackgroundView *weakSelf = self;
|
|
self.drawRect = ^ (TUIView *view, CGRect rect)
|
|
{
|
|
TUIPopoverBackgroundView *strongSelf = weakSelf;
|
|
CGContextRef context = TUIGraphicsGetCurrentContext();
|
|
CGPathRef outerBorder = [strongSelf newPopoverPathForEdge:strongSelf.popoverEdge inFrame:strongSelf.bounds];
|
|
CGContextSetStrokeColorWithColor(context, strongSelf.strokeColor.tui_CGColor);
|
|
CGContextAddPath(context, outerBorder);
|
|
CGContextStrokePath(context);
|
|
|
|
CGContextSetFillColorWithColor(context, strongSelf.fillColor.tui_CGColor);
|
|
CGContextAddPath(context, outerBorder);
|
|
CGContextFillPath(context);
|
|
|
|
CGPathRelease(outerBorder);
|
|
};
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)updateMaskLayer
|
|
{
|
|
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
|
CGPathRef path = [self newPopoverPathForEdge:self.popoverEdge inFrame:self.bounds];
|
|
maskLayer.path = path;
|
|
maskLayer.fillColor = CGColorGetConstantColor(kCGColorBlack);
|
|
|
|
CGPathRelease(path);
|
|
|
|
self.layer.mask = maskLayer;
|
|
|
|
}
|
|
|
|
- (CGRectEdge)arrowEdgeForPopoverEdge:(CGRectEdge)popoverEdge
|
|
{
|
|
CGRectEdge arrowEdge = CGRectMinYEdge;
|
|
switch (popoverEdge) {
|
|
case CGRectMaxXEdge:
|
|
arrowEdge = CGRectMinXEdge;
|
|
break;
|
|
case CGRectMaxYEdge:
|
|
arrowEdge = CGRectMinYEdge;
|
|
break;
|
|
case CGRectMinXEdge:
|
|
arrowEdge = CGRectMaxXEdge;
|
|
break;
|
|
case CGRectMinYEdge:
|
|
arrowEdge = CGRectMaxYEdge;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return arrowEdge;
|
|
}
|
|
|
|
@end
|
|
|
|
// Hmm I'm not sure I like how this takes some of the drawing responsibility away from the background view breaking the extensibility.
|
|
// But it works.
|
|
|
|
@implementation TUIPopoverWindowContentView
|
|
|
|
@synthesize nsView = _nsView;
|
|
@synthesize arrowEdge = _arrowEdge;;
|
|
|
|
- (id)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:frameRect];
|
|
if(self == nil) return nil;
|
|
|
|
_arrowEdge = CGRectMinYEdge;
|
|
_nsView = [[TUINSView alloc] initWithFrame:self.bounds];
|
|
[self.nsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
|
[self.nsView tui_setOpaque:NO];
|
|
[self addSubview:self.nsView];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isOpaque {
|
|
return NO;
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
[NSGraphicsContext saveGraphicsState];
|
|
|
|
|
|
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
|
[[NSColor whiteColor] set];
|
|
|
|
CGRect targetRect = CGRectZero;
|
|
switch (self.arrowEdge) {
|
|
case CGRectMinYEdge:
|
|
targetRect = CGRectMake(1.0f, 1.0f + TUIPopoverBackgroundViewArrowHeight, CGRectGetWidth(self.bounds) - 2.0f, CGRectGetHeight(self.bounds) - TUIPopoverBackgroundViewArrowHeight - 2.0f);
|
|
break;
|
|
case CGRectMaxXEdge:
|
|
targetRect = CGRectMake(1.0f, 1.0f, CGRectGetWidth(self.bounds) - 2.0f - TUIPopoverBackgroundViewArrowHeight, CGRectGetHeight(self.bounds) - 2.0f);
|
|
break;
|
|
case CGRectMaxYEdge:
|
|
targetRect = CGRectMake(1.0f, 1.0f, CGRectGetWidth(self.bounds) - 2.0f, CGRectGetHeight(self.bounds) - 2.0f - TUIPopoverBackgroundViewArrowHeight);
|
|
break;
|
|
case CGRectMinXEdge:
|
|
targetRect = CGRectMake(TUIPopoverBackgroundViewArrowHeight + 1.0f, 1.0f, CGRectGetWidth(self.bounds) - 2.0f - TUIPopoverBackgroundViewArrowHeight, CGRectGetHeight(self.bounds) - 2.0f);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CGContextFillRoundRect(context, targetRect, TUIPopoverBackgroundViewBorderRadius);
|
|
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
}
|
|
|
|
@end
|