From 1a8fc7f07f7522ea0d9c8d8a3a8d92e57e67daab Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Fri, 23 Mar 2012 19:53:46 -0700 Subject: [PATCH] CPPopover fixes/enhancements - Lion-style popovers now zoom open just like their Lion counterparts (except for opacity, the browser couldn't handle that at the same time). - Fixed a number of subtle drawing issues caused by not aligning path points correctly. - The anchor arrow deals correctly with corner cases where the middle of the reference rect is not within the corners of the popover. - Code cleanup: factored redundant code, removed unused code. - Added corner cases to the demo app, changed demo app to reuse a single popover. --- AppKit/CPWindow/_CPAttachedWindowView.j | 286 ++++++++++++++++-------- AppKit/_CPAttachedWindow.j | 167 ++++++++------ Tests/Manual/CPPopover/AppController.j | 150 ++++++++----- 3 files changed, 381 insertions(+), 222 deletions(-) diff --git a/AppKit/CPWindow/_CPAttachedWindowView.j b/AppKit/CPWindow/_CPAttachedWindowView.j index 7ab660aab..fcf179645 100644 --- a/AppKit/CPWindow/_CPAttachedWindowView.j +++ b/AppKit/CPWindow/_CPAttachedWindowView.j @@ -22,11 +22,15 @@ @import "_CPWindowView.j" +#define ALIGN_STROKE(point) (FLOOR(point) === (point) ? (point) + halfStrokeWidth : (point)) +#define ALIGN_COORD(point) (FLOOR(point)) + +var _CPAttachedWindowViewDefaultCursorSize = CGSizeMake(16, 10); /*! @ignore - A custom CPWindowView that manage border and cursor + A custom CPWindowView that manages a border and cursor */ @implementation _CPAttachedWindowView : _CPWindowView { @@ -36,7 +40,7 @@ int _appearance @accessors(property=appearance); unsigned _preferredEdge @accessors(property=preferredEdge); - CPSize _cursorSize; + CGSize _cursorSize; } /*! @@ -81,18 +85,16 @@ /*! Initialize the _CPWindowView */ -- (id)initWithFrame:(CPRect)aFrame styleMask:(unsigned)aStyleMask +- (id)initWithFrame:(CGRect)aFrame styleMask:(unsigned)aStyleMask { if (self = [super initWithFrame:aFrame styleMask:aStyleMask]) { - var bundle = [CPBundle bundleForClass:[self class]]; _arrowOffsetX = 0.0; _arrowOffsetY = 0.0; // @TODO: make this themable - _useGlowingEffect = YES; _appearance = CPPopoverAppearanceMinimal; - _cursorSize = CPSizeMake(15, 10); + _cursorSize = CGSizeMakeCopy(_CPAttachedWindowViewDefaultCursorSize); } return self; @@ -103,7 +105,7 @@ */ - (void)hideCursor { - _cursorSize = CPSizeMakeZero(); + _cursorSize = CGSizeMakeZero(); [self setNeedsDisplay:YES]; } @@ -112,7 +114,7 @@ */ - (void)showCursor { - _cursorSize = CPSizeMake(15, 10); + _cursorSize = CGSizeMakeCopy(_CPAttachedWindowViewDefaultCursorSize); [self setNeedsDisplay:YES]; _mouseDownPressed = NO; } @@ -129,11 +131,13 @@ arrowWidth = _cursorSize.width, arrowHeight = _cursorSize.height, strokeWidth = 1, + halfStrokeWidth = strokeWidth / 2.0, strokeColor, shadowColor = [[CPColor blackColor] colorWithAlphaComponent:.2], - shadowSize = CGSizeMake(0, 7), + shadowSize = CGSizeMake(0, 6), shadowBlur = 15, - gradient; + gradient, + frame = [self bounds]; if (_appearance == CPPopoverAppearanceMinimal) { @@ -149,133 +153,217 @@ } // fix rect to take care of stroke and shadow - aRect.origin.x += strokeWidth + shadowBlur; - aRect.origin.y += strokeWidth + (shadowBlur + shadowSize.height / 2); - aRect.size.width -= (strokeWidth * 2) + (shadowBlur * 2); - aRect.size.height -= (strokeWidth * 2) + (shadowBlur * 2 + shadowSize.height); + frame.origin.x += halfStrokeWidth + shadowBlur; + frame.origin.y += halfStrokeWidth + (shadowBlur + shadowSize.height / 2); + frame.size.width -= strokeWidth + (shadowBlur * 2); + frame.size.height -= strokeWidth + (shadowBlur * 2 + shadowSize.height); CGContextSetStrokeColor(context, strokeColor); CGContextSetLineWidth(context, strokeWidth); CGContextBeginPath(context); CGContextSetShadowWithColor(context, shadowSize, shadowBlur, shadowColor); - CGContextDrawLinearGradient(context, gradient, CGPointMake(CPRectGetMidX(aRect), 0.0), CGPointMake(CPRectGetMidX(aRect), aRect.size.height), 0); + CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), 0.0), CGPointMake(CGRectGetMidX(frame), frame.size.height), 0); - - var xMin = _CGRectGetMinX(aRect), - xMax = _CGRectGetMaxX(aRect), - yMin = _CGRectGetMinY(aRect), - yMax = _CGRectGetMaxY(aRect); + var xMin = _CGRectGetMinX(frame), + xMax = _CGRectGetMaxX(frame), + yMin = _CGRectGetMinY(frame), + yMax = _CGRectGetMaxY(frame), + arrowMinX = ALIGN_COORD(xMin + radius + strokeWidth), + arrowMaxX = ALIGN_COORD(xMax - radius - strokeWidth), + arrowMinY = ALIGN_COORD(yMin + radius + strokeWidth), + arrowMaxY = ALIGN_COORD(yMax - radius + strokeWidth), + arrowAnchor = CGPointMakeZero(), + arrowStart = CGPointMakeZero(), + pt = CGPointMakeZero(); // draw! switch (_preferredEdge) { case CPMinXEdge: - // origin ne - CGContextMoveToPoint(context, xMin + radius, yMin); - - // ne - CGContextAddLineToPoint(context, xMax - radius, yMin); - CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); - - // arrow CPMinXEdge - CGContextAddLineToPoint(context, xMax, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY - (arrowHeight - 2)); - CGContextAddLineToPoint(context, aRect.size.width + arrowHeight + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY); - CGContextAddLineToPoint(context, aRect.size.width + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 + (arrowWidth / 2)) + aRect.origin.y + _arrowOffsetY); - - // se - CGContextAddLineToPoint(context, xMax, yMax - radius); - CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); - - // sw - CGContextAddLineToPoint(context, xMin + radius, yMax); - CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); - - // nw - CGContextAddLineToPoint(context, xMin, yMin + radius); - CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); - break; - case CPMaxXEdge: - // origin ne - CGContextMoveToPoint(context, xMin + radius, yMin); + // origin nw + pt.x = ALIGN_COORD(xMin + radius); + pt.y = yMin; + CGContextMoveToPoint(context, pt.x, pt.y); // ne - CGContextAddLineToPoint(context, xMax - radius, yMin); - CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + pt.x = ALIGN_COORD(xMax - radius); + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, xMax, yMin, xMax, ALIGN_COORD(yMin + radius)); + + if (_preferredEdge === CPMinXEdge) + { + // arrow CPMinXEdge + arrowAnchor.x = ALIGN_STROKE(xMax); + arrowAnchor.y = ALIGN_COORD((frame.size.height / 2) + yMin + _arrowOffsetY); + + // to top edge + pt.y = ALIGN_COORD(arrowAnchor.y - (arrowWidth / 2)); + + // adjust starting point to not go beyond the corner + if (pt.y <= arrowMinY) + pt.y = arrowMinY; + else if ((pt.y + arrowWidth) > arrowMaxY) + pt.y = arrowMaxY - arrowWidth; + + pt.x = arrowAnchor.x; + arrowStart = CGPointMakeCopy(pt); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // top edge -> point + pt.x = ALIGN_STROKE(arrowAnchor.x + arrowHeight); + pt.y = arrowAnchor.y; + CGContextAddLineToPoint(context, pt.x, pt.y); + + // point -> bottom edge + pt.x = arrowAnchor.x; + pt.y = ALIGN_COORD(arrowStart.y + arrowWidth); + CGContextAddLineToPoint(context, pt.x, pt.y); + } // se - CGContextAddLineToPoint(context, xMax, yMax - radius); - CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + pt.x = xMax; + pt.y = ALIGN_COORD(yMax - radius); + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, pt.x, yMax, ALIGN_COORD(xMax - radius), yMax); // sw - CGContextAddLineToPoint(context, xMin + radius, yMax); - CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + pt.x = ALIGN_COORD(xMin + radius); + pt.y = yMax; + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, xMin, pt.y, xMin, ALIGN_COORD(yMax - radius)); - // arrow CPMaxXEdge - CGContextAddLineToPoint(context, xMin, (aRect.size.height / 2 + (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY)); - CGContextAddLineToPoint(context, aRect.origin.x - arrowHeight + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY); - CGContextAddLineToPoint(context, aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 - (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY)); + if (_preferredEdge === CPMaxXEdge) + { + // arrow CPMaxXEdge + arrowAnchor.x = ALIGN_STROKE(xMin); + arrowAnchor.y = ALIGN_COORD((frame.size.height / 2) + yMin + _arrowOffsetY); + + // to bottom edge + pt.y = ALIGN_COORD(arrowAnchor.y + (arrowWidth / 2)); + + // adjust starting point to not go beyond the corner + if ((pt.y - arrowWidth) < arrowMinY) + pt.y = arrowMinY + arrowWidth; + else if (pt.y > arrowMaxY) + pt.y = arrowMaxY; + + pt.x = arrowAnchor.x; + arrowStart = CGPointMakeCopy(pt); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // bottom edge -> point + pt.x = ALIGN_STROKE(arrowAnchor.x - arrowHeight); + pt.y = arrowAnchor.y; + CGContextAddLineToPoint(context, pt.x, pt.y); + + // point -> top edge + pt.x = arrowAnchor.x; + pt.y = ALIGN_COORD(arrowStart.y - arrowWidth); + CGContextAddLineToPoint(context, pt.x, pt.y); + } // nw - CGContextAddLineToPoint(context, xMin, yMin + radius); - CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + pt.x = xMin; + pt.y = ALIGN_COORD(yMin + radius); + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, pt.x, yMin, ALIGN_COORD(xMin + radius), yMin); break; case CPMaxYEdge: - // origin nw - CGContextMoveToPoint(context, xMin, yMin + yMin); - - // nw - CGContextAddLineToPoint(context, xMin, yMin + radius); - CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); - - // arrow CPMaxYEdge - CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX - (arrowWidth / 2), yMin); - CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y - arrowHeight + _arrowOffsetY); - CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y + _arrowOffsetY); - - // ne - CGContextAddLineToPoint(context, xMax - radius, yMin); - CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); - - // se - CGContextAddLineToPoint(context, xMax, yMax - radius); - CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); - - // sw - CGContextAddLineToPoint(context, xMin + radius, yMax); - CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); - break; - case CPMinYEdge: - // origin nw - CGContextMoveToPoint(context, xMin, yMin + yMin); + // origin sw + pt.x = xMin; + pt.y = ALIGN_COORD(yMax - radius); + CGContextMoveToPoint(context, pt.x, pt.y); // nw - CGContextAddLineToPoint(context, xMin, yMin + radius); - CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + pt.y = ALIGN_COORD(yMin + radius); + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, pt.x, yMin, ALIGN_COORD(xMin + radius), yMin); + + if (_preferredEdge === CPMaxYEdge) + { + // arrow CPMaxYEdge + arrowAnchor.x = ALIGN_COORD((frame.size.width / 2) + xMin + _arrowOffsetX); + arrowAnchor.y = ALIGN_STROKE(yMin + _arrowOffsetY); + + // to left edge + pt.x = ALIGN_COORD(arrowAnchor.x - (arrowWidth / 2)); + + // adjust starting point to not go beyond the corner + if (pt.x < arrowMinX) + pt.x = arrowMinX; + else if ((pt.x + arrowWidth) > arrowMaxX) + pt.x = arrowMaxX - arrowWidth; + + pt.y = arrowAnchor.y; + arrowStart = CGPointMakeCopy(pt); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // left edge -> point + pt.x = arrowAnchor.x; + pt.y = ALIGN_STROKE(arrowAnchor.y - arrowHeight); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // point -> right edge + pt.x = ALIGN_COORD(arrowStart.x + arrowWidth); + pt.y = arrowAnchor.y; + CGContextAddLineToPoint(context, pt.x, pt.y); + } // ne - CGContextAddLineToPoint(context, xMax - radius, yMin); - CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + pt.x = ALIGN_COORD(xMax - radius); + pt.y = yMin; + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, xMax, pt.y, xMax, ALIGN_COORD(yMin + radius)); // se - CGContextAddLineToPoint(context, xMax, yMax - radius); - CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + pt.x = xMax; + pt.y = ALIGN_COORD(yMax - radius); + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, pt.x, yMax, ALIGN_COORD(xMax - radius), yMax); - // arrow CPMinYEdge - CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX , yMax); - CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + arrowHeight + _arrowOffsetY); - CGContextAddLineToPoint(context, (aRect.size.width / 2) - (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + _arrowOffsetY); + if (_preferredEdge === CPMinYEdge) + { + // arrow CPMinYEdge + arrowAnchor.x = ALIGN_COORD((frame.size.width / 2) + xMin + _arrowOffsetX); + arrowAnchor.y = ALIGN_STROKE(yMax + _arrowOffsetY); + + // to right edge + pt.x = ALIGN_COORD(arrowAnchor.x + (arrowWidth / 2)); + + // adjust starting point to not go beyond the corner + if ((pt.x - arrowWidth) < arrowMinX) + pt.x = arrowMinX + arrowWidth; + else if (pt.x > arrowMaxX) + pt.x = arrowMaxX; + + pt.y = arrowAnchor.y; + arrowStart = CGPointMakeCopy(pt); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // right edge -> point + pt.x = arrowAnchor.x; + pt.y = ALIGN_STROKE(arrowAnchor.y + arrowHeight); + CGContextAddLineToPoint(context, pt.x, pt.y); + + // point -> left edge + pt.x = ALIGN_COORD(arrowStart.x - arrowWidth); + pt.y = arrowAnchor.y; + CGContextAddLineToPoint(context, pt.x, pt.y); + } // sw - CGContextAddLineToPoint(context, xMin + radius, yMax); - CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + pt.x = ALIGN_COORD(xMin + radius); + pt.y = yMax; + CGContextAddLineToPoint(context, pt.x, pt.y); + CGContextAddCurveToPoint(context, pt.x, pt.y, xMin, pt.y, xMin, ALIGN_COORD(yMax - radius)); break; default: // no computed edge means standard rounded rect - CGContextAddPath(context, CGPathWithRoundedRectangleInRect(aRect, radius, radius, YES, YES, YES, YES)); + CGContextAddPath(context, CGPathWithRoundedRectangleInRect(frame, radius, radius, YES, YES, YES, YES)); } CGContextClosePath(context); diff --git a/AppKit/_CPAttachedWindow.j b/AppKit/_CPAttachedWindow.j index c3fa0e670..6919c6dd4 100644 --- a/AppKit/_CPAttachedWindow.j +++ b/AppKit/_CPAttachedWindow.j @@ -49,9 +49,9 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, BOOL _closeOnBlur; BOOL _isClosed; + BOOL _browserAnimates; BOOL _shouldPerformAnimation; CPButton _closeButton; - float _animationDuration; CPInteger _implementedDelegateMethods; } @@ -128,18 +128,16 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, { _animates = YES; _animationStyle = CPPopoverAnimationStyleLion; - _animationDuration = 150; _closeOnBlur = (aStyleMask & CPClosableOnBlurWindowMask); _isClosed = NO; + _browserAnimates = [self browserSupportsAnimation]; _shouldPerformAnimation = _animates; [self setLevel:CPStatusWindowLevel]; [self setMovableByWindowBackground:YES]; [self setHasShadow:NO]; - _DOMElement.style.webkitBackfaceVisibility = "hidden"; - _DOMElement.style.webkitTransitionProperty = "-webkit-transform, opacity"; - _DOMElement.style.webkitTransitionDuration = _animationDuration + "ms"; + [self setCSS3Property:@"TransitionProperty" value:@"-webkit-transform, opacity"]; [_windowView setNeedsDisplay:YES]; } @@ -247,23 +245,24 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, originLeft = CGPointCreateCopy(aRect.origin), originRight = CGPointCreateCopy(aRect.origin), originTop = CGPointCreateCopy(aRect.origin), - originBottom = CGPointCreateCopy(aRect.origin); + originBottom = CGPointCreateCopy(aRect.origin), + frameSize = [self frame].size; // CPMaxXEdge originRight.x += aRect.size.width; - originRight.y += (aRect.size.height / 2.0) - (CGRectGetHeight([self frame]) / 2.0); + originRight.y += (aRect.size.height / 2.0) - (frameSize.height / 2.0); // CPMinXEdge - originLeft.x -= CGRectGetWidth([self frame]); - originLeft.y += (aRect.size.height / 2.0) - (CGRectGetHeight([self frame]) / 2.0); + originLeft.x -= frameSize.width; + originLeft.y += (aRect.size.height / 2.0) - (frameSize.height / 2.0); // CPMaxYEdge - originBottom.x += aRect.size.width / 2.0 - CGRectGetWidth([self frame]) / 2.0; + originBottom.x += aRect.size.width / 2.0 - frameSize.width / 2.0; originBottom.y += aRect.size.height; // CPMinYEdge - originTop.x += aRect.size.width / 2.0 - CGRectGetWidth([self frame]) / 2.0; - originTop.y -= CGRectGetHeight([self frame]); + originTop.x += aRect.size.width / 2.0 - frameSize.width / 2.0; + originTop.y -= frameSize.height; var requestedEdge = (anEdge !== nil) ? anEdge : CPMaxXEdge, requestedOrigin; @@ -301,20 +300,20 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, [_windowView setArrowOffsetX:o.x]; o.x = 0; } - if (o.x + CGRectGetWidth([self frame]) > nativeRect.size.width) + if (o.x + frameSize.width > nativeRect.size.width) { - [_windowView setArrowOffsetX:(o.x + CGRectGetWidth([self frame]) - nativeRect.size.width)]; - o.x = nativeRect.size.width - CGRectGetWidth([self frame]); + [_windowView setArrowOffsetX:(o.x + frameSize.width - nativeRect.size.width)]; + o.x = nativeRect.size.width - frameSize.width; } if (o.y < 0) { [_windowView setArrowOffsetY:o.y]; o.y = 0; } - if (o.y + CGRectGetHeight([self frame]) > nativeRect.size.height) + if (o.y + frameSize.height > nativeRect.size.height) { - [_windowView setArrowOffsetY:(CGRectGetHeight([self frame]) + o.y - nativeRect.size.height)]; - o.y = nativeRect.size.height - CGRectGetHeight([self frame]); + [_windowView setArrowOffsetY:(frameSize.height + o.y - nativeRect.size.height)]; + o.y = nativeRect.size.height - frameSize.height; } switch (g) @@ -324,7 +323,7 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, return o; break; case CPMinXEdge: - if ((o.x + _frame.size.width) <= aRect.origin.x) + if ((o.x + frameSize.width) <= aRect.origin.x) return o; break; case CPMaxYEdge: @@ -332,7 +331,7 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, return o; break; case CPMinYEdge: - if ((o.y + _frame.size.height) <= aRect.origin.y) + if ((o.y + frameSize.height) <= aRect.origin.y) return o; break; } @@ -403,6 +402,27 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, [self makeKeyAndOrderFront:nil]; } +/*! @ignore */ +- (void)setCSS3Property:(CPString)property value:(CPString)value +{ + _DOMElement.style['webkit' + property] = value; + + // Support other browsers here eventually +} + +/*! @ignore */ +- (BOOL)browserSupportsAnimation +{ + return typeof(_DOMElement.style.webkitTransition) !== "undefined"; + + /* + No others browsers supported yet. + + typeof(_DOMElement.style.MozTransition) !== "undefined" || + typeof(_DOMElement.style.MsTransition) !== "undefined" || + typeof(_DOMElement.style.OTransition) !== "undefined"; + */ +} #pragma mark - #pragma mark Actions @@ -443,63 +463,65 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, { [super orderFront:aSender]; - var transformOrigin = "50% 100%"; - - if (_animationStyle === CPPopoverAnimationStyleLion) + if (_animates && _browserAnimates && _shouldPerformAnimation) { - switch ([_windowView preferredEdge]) - { - case CPMaxYEdge: - var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width); - transformOrigin = posX + "% 0%"; // 50 0 - break; - case CPMinYEdge: - var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width); - transformOrigin = posX + "% 100%"; // 50 100 - break; - case CPMinXEdge: - var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height); - transformOrigin = "100% " + posY + "%"; // 100 50 - break; - case CPMaxXEdge: - var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height); - transformOrigin = "0% "+ posY + "%"; // 0 50 - break; - } - } - - // @TODO: implement for FF - if (_animates && _shouldPerformAnimation && typeof(_DOMElement.style.webkitTransform) != "undefined") - { - _DOMElement.style.opacity = 0; - if (_animationStyle === CPPopoverAnimationStyleLion) { - _DOMElement.style.webkitTransform = "scale(0)"; - _DOMElement.style.webkitTransformOrigin = transformOrigin; - } + var transformOrigin = "50% 100%", + frame = [self frame], + preferredEdge = [_windowView preferredEdge], + posX; - window.setTimeout(function() - { - _DOMElement.style.opacity = 1; - _DOMElement.style.height = _frame.size.height + @"px"; - _DOMElement.style.width = _frame.size.width + @"px"; - - if (_animationStyle === CPPopoverAnimationStyleLion) + switch (preferredEdge) { - _DOMElement.style.webkitTransform = "scale(1.1)"; - _DOMElement.style.webkitTransitionDuration = "250ms"; - _DOMElement.style.webkitTransitionTimingFunction = "ease-out"; + case CPMaxYEdge: + case CPMinYEdge: + posX = 50 + (([_windowView arrowOffsetX] * 100) / frame.size.width); + transformOrigin = posX + "% " + (preferredEdge === CPMaxYEdge ? "0%" : "100%"); + break; - var transitionEndFunction = function() - { - _DOMElement.style.webkitTransform = "scale(1)"; - _DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES); - }; - - _DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES); + case CPMinXEdge: + case CPMaxXEdge: + posY = 50 + (([_windowView arrowOffsetY] * 100) / frame.size.height); + transformOrigin = (preferredEdge === CPMaxXEdge ? "0% " : "100% ") + posY + "%"; // 100 50 + break; } - }, 0); + + // This is the initial transform + [self setCSS3Property:@"Transform" value:@"scale(0)"]; + [self setCSS3Property:@"TransformOrigin" value:transformOrigin]; + [self setCSS3Property:@"TransitionDuration" value:"0"]; + + window.setTimeout(function() + { + if (_animationStyle === CPPopoverAnimationStyleLion) + { + // We are watching opacity, so this triggers the next transition + _DOMElement.style.opacity = 1; + _DOMElement.style.height = frame.size.height + @"px"; + _DOMElement.style.width = frame.size.width + @"px"; + + // Set up the pop-out transition + [self setCSS3Property:@"Transform" value:@"scale(1.1)"]; + [self setCSS3Property:@"TransitionDuration" value:@"200ms"]; + [self setCSS3Property:@"TransitionTimingFunction" value:@"ease-in"]; + + var transitionEndFunction = function() + { + _DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES); + + // Now set up the pop-in to normal size transition + [self setCSS3Property:@"Transform" value:@"scale(1)"]; + [self setCSS3Property:@"TransitionDuration" value:@"50ms"]; + [self setCSS3Property:@"TransitionTimingFunction" value:@"linear"]; + }; + + _DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES); + } + }, 0); + } + else + _DOMElement.style.opacity = 1; } [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_attachedWindowDidMove:) name:CPWindowDidMoveNotification object:self]; @@ -516,11 +538,10 @@ var _CPAttachedWindow_attachedWindowShouldClose_ = 1 << 0, // set a close flag to avoid infinite loop _isClosed = YES; - if (_animates && typeof(_DOMElement.style.webkitTransform) != "undefined") + if (_animates && _browserAnimates) { + [self setCSS3Property:@"Transition" value:@"opacity 250ms linear"]; _DOMElement.style.opacity = 0; - _DOMElement.style.webkitTransitionDuration = "250ms"; - _DOMElement.style.webkitTransitionTimingFunction = "linear"; var transitionEndFunction = function() { diff --git a/Tests/Manual/CPPopover/AppController.j b/Tests/Manual/CPPopover/AppController.j index e2b2623c8..47907b48e 100644 --- a/Tests/Manual/CPPopover/AppController.j +++ b/Tests/Manual/CPPopover/AppController.j @@ -12,39 +12,72 @@ @implementation AppController : CPObject { - CPPopUpButton buttonGravity; + CPPopUpButton buttonEdge; CPPopUpButton buttonStyle; CPPopUpButton buttonAnimation; CPPopUpButton buttonBehaviour; + CPPopover popover; + CPTextField appearanceLabel; } - (void)applicationDidFinishLaunching:(CPNotification)aNotification { var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], - contentView = [theWindow contentView]; + contentView = [theWindow contentView], + contentViewSize = [contentView frameSize]; var button = [CPButton buttonWithTitle:@"click"]; [button setTarget:self]; [button setAction:@selector(open:)]; - [button setFrameOrigin:CPPointMake(10, 60)]; + [button setFrameOrigin:CGPointMake(1, 1)]; [contentView addSubview:button]; button = [CPButton buttonWithTitle:@"click"]; [button setTarget:self]; [button setAction:@selector(open:)]; - [button setFrameOrigin:CPPointMake( [contentView frameSize].width - 50, 60)]; + [button setFrameOrigin:CGPointMake(1, 100)]; [contentView addSubview:button]; button = [CPButton buttonWithTitle:@"click"]; [button setTarget:self]; [button setAction:@selector(open:)]; - [button setFrameOrigin:CPPointMake( [contentView frameSize].width - 50, [contentView frameSize].height - 50)]; + [button setAutoresizingMask:CPViewMinXMargin] + [button setFrameOrigin:CGPointMake(contentViewSize.width - CGRectGetWidth([button frame]) - 1, 1)]; [contentView addSubview:button]; button = [CPButton buttonWithTitle:@"click"]; [button setTarget:self]; [button setAction:@selector(open:)]; - [button setFrameOrigin:CPPointMake( 10, [contentView frameSize].height - 50)]; + [button setAutoresizingMask:CPViewMinXMargin] + [button setFrameOrigin:CGPointMake(contentViewSize.width - CGRectGetWidth([button frame]) - 1, 100)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setAutoresizingMask:CPViewMinXMargin | CPViewMinYMargin] + [button setFrameOrigin:CGPointMake(contentViewSize.width - CGRectGetWidth([button frame])- 1, contentViewSize.height - CGRectGetHeight([button frame]) - 1)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setAutoresizingMask:CPViewMinXMargin | CPViewMinYMargin] + [button setFrameOrigin:CGPointMake(contentViewSize.width - CGRectGetWidth([button frame])- 1, contentViewSize.height - 100)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setAutoresizingMask:CPViewMaxXMargin | CPViewMinYMargin] + [button setFrameOrigin:CGPointMake(1, contentViewSize.height - CGRectGetHeight([button frame]) - 1)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setAutoresizingMask:CPViewMaxXMargin | CPViewMinYMargin] + [button setFrameOrigin:CGPointMake(1, contentViewSize.height - 100)]; [contentView addSubview:button]; button = [CPButton buttonWithTitle:@"click"]; @@ -55,30 +88,30 @@ [contentView addSubview:button]; - buttonGravity = [[CPPopUpButton alloc] initWithFrame:CPRectMake(10, 10, 130, 24)]; - [buttonGravity addItemWithTitle:"Automatic"]; - [buttonGravity addItemWithTitle:"Bottom"]; - [buttonGravity addItemWithTitle:"Top"]; - [buttonGravity addItemWithTitle:"Right"]; - [buttonGravity addItemWithTitle:"Left"]; - [contentView addSubview:buttonGravity]; + buttonEdge = [[CPPopUpButton alloc] initWithFrame:CGRectMake(150, 10, 130, 24)]; + [buttonEdge addItemWithTitle:"Automatic"]; + [buttonEdge addItemWithTitle:"Bottom"]; + [buttonEdge addItemWithTitle:"Top"]; + [buttonEdge addItemWithTitle:"Right"]; + [buttonEdge addItemWithTitle:"Left"]; + [contentView addSubview:buttonEdge]; - buttonStyle = [[CPPopUpButton alloc] initWithFrame:CPRectMake(150, 10, 130, 24)]; + buttonStyle = [[CPPopUpButton alloc] initWithFrame:CGRectMake(290, 10, 130, 24)]; [buttonStyle addItemWithTitle:"Minimal"]; [buttonStyle addItemWithTitle:"HUD"]; [contentView addSubview:buttonStyle]; - buttonAnimation = [[CPPopUpButton alloc] initWithFrame:CPRectMake(290, 10, 130, 24)]; + buttonAnimation = [[CPPopUpButton alloc] initWithFrame:CGRectMake(430, 10, 130, 24)]; [buttonAnimation addItemWithTitle:"With animation"]; [buttonAnimation addItemWithTitle:"No animation"]; [contentView addSubview:buttonAnimation]; - buttonAnimationStyle = [[CPPopUpButton alloc] initWithFrame:CPRectMake(430, 10, 130, 24)]; + buttonAnimationStyle = [[CPPopUpButton alloc] initWithFrame:CGRectMake(570, 10, 130, 24)]; [buttonAnimationStyle addItemWithTitle:"Lion"]; [buttonAnimationStyle addItemWithTitle:"iOS"]; [contentView addSubview:buttonAnimationStyle]; - buttonBehaviour = [[CPPopUpButton alloc] initWithFrame:CPRectMake(570, 10, 130, 24)]; + buttonBehaviour = [[CPPopUpButton alloc] initWithFrame:CGRectMake(710, 10, 130, 24)]; [buttonBehaviour addItemWithTitle:"Transient"]; [buttonBehaviour addItemWithTitle:"Not managed"]; [contentView addSubview:buttonBehaviour]; @@ -88,64 +121,81 @@ - (IBAction)open:(id)sender { - var g; - switch ([buttonGravity title]) + var edge; + + switch ([buttonEdge title]) { case "Automatic": - g = nil; + edge = nil; break; case "Bottom": - g = CPMaxYEdge; + edge = CPMaxYEdge; break; case "Top": - g = CPMinYEdge; + edge = CPMinYEdge; break; case "Left": - g = CPMinXEdge; + edge = CPMinXEdge; break; case "Right": - g = CPMaxXEdge; + edge = CPMaxXEdge; break; } - var a; + var appearance; + switch ([buttonStyle title]) { case "Minimal": - a = CPPopoverAppearanceMinimal; + appearance = CPPopoverAppearanceMinimal; break; case "HUD": - a = CPPopoverAppearanceHUD; + appearance = CPPopoverAppearanceHUD; break; } - var p = [[CPPopover alloc] init], - viewC = [[CPViewController alloc] init], - view = [[CPView alloc] initWithFrame:CPRectMake(0.0, 0.0, 320, 300)], - label = [CPTextField labelWithTitle:[buttonGravity title]]; + var pop = [self popoverWithAppearance:appearance]; - [label setFont:[CPFont boldSystemFontOfSize:30.0]]; - [label setFrameOrigin:CPPointMake(0, 70)]; - [label setValue:(a === CPPopoverAppearanceHUD) ? [CPColor colorWithHexString:@"333"] : [CPColor colorWithHexString:@"fff"] forThemeAttribute:@"text-shadow-color"]; - [label setValue:CGSizeMake(0.0, 1.0) forThemeAttribute:@"text-shadow-offset"]; - [label setTextColor:(a === CPPopoverAppearanceHUD) ? [CPColor whiteColor] : [CPColor colorWithHexString:@"444"]]; - [label setFrameSize:CPSizeMake([view frame].size.width, 50)]; - [label setAlignment:CPCenterTextAlignment]; - [view addSubview:label]; + [pop showRelativeToRect:nil ofView:sender preferredEdge:edge]; - [viewC setView:view]; - [p setContentViewController:viewC]; - [p setAnimates:([buttonAnimation title] === @"With animation")]; - [p setAnimationStyle:[buttonAnimationStyle title] === @"Lion" ? CPPopoverAnimationStyleLion : CPPopoverAnimationStyleIOS]; - [p setBehaviour:([buttonBehaviour title] === @"Transient") ? CPPopoverBehaviorTransient : CPPopoverBehaviorApplicationDefined]; - [p setAppearance:a]; - [p setDelegate:self]; - [p showRelativeToRect:nil ofView:sender preferredEdge:g]; - CPLog.info("content size - w:" + [p contentSize].width + " h:" + [p contentSize].width); - CPLog.info("positioning rect - x: " + [p positioningRect].origin.x + " y: " + [p positioningRect].origin.x - + " w:" + [p positioningRect].size.width + " h:" + [p positioningRect].size.width); + CPLog.info("content size - w:" + [pop contentSize].width + " h:" + [pop contentSize].width); + CPLog.info("positioning rect - x: " + [pop positioningRect].origin.x + " y: " + [pop positioningRect].origin.x + + " w:" + [pop positioningRect].size.width + " h:" + [pop positioningRect].size.width); } +- (CPPopover)popoverWithAppearance:(int)appearance +{ + if (popover) + { + [appearanceLabel setStringValue:[buttonEdge title]]; + return popover; + } + + popover = [CPPopover new]; + + var controller = [[CPViewController alloc] init], + view = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320, 300)]; + + appearanceLabel = [CPTextField labelWithTitle:[buttonEdge title]]; + [appearanceLabel setFont:[CPFont boldSystemFontOfSize:30.0]]; + [appearanceLabel setFrameOrigin:CGPointMake(0, 70)]; + [appearanceLabel setValue:(appearance === CPPopoverAppearanceHUD) ? [CPColor colorWithHexString:@"333"] : [CPColor colorWithHexString:@"fff"] forThemeAttribute:@"text-shadow-color"]; + [appearanceLabel setValue:CGSizeMake(0.0, 1.0) forThemeAttribute:@"text-shadow-offset"]; + [appearanceLabel setTextColor:(appearance === CPPopoverAppearanceHUD) ? [CPColor whiteColor] : [CPColor colorWithHexString:@"444"]]; + [appearanceLabel setFrameSize:CPSizeMake([view frame].size.width, 50)]; + [appearanceLabel setAlignment:CPCenterTextAlignment]; + [view addSubview:appearanceLabel]; + + [controller setView:view]; + [popover setContentViewController:controller]; + [popover setAnimates:([buttonAnimation title] === @"With animation")]; + [popover setAnimationStyle:[buttonAnimationStyle title] === @"Lion" ? CPPopoverAnimationStyleLion : CPPopoverAnimationStyleIOS]; + [popover setBehaviour:([buttonBehaviour title] === @"Transient") ? CPPopoverBehaviorTransient : CPPopoverBehaviorApplicationDefined]; + [popover setAppearance:appearance]; + [popover setDelegate:self]; + + return popover; +} #pragma mark - #pragma mark CPPopover Delegate