Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / page / mac / TextIndicatorWindow.mm
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "TextIndicatorWindow.h"
28
29 #if PLATFORM(MAC)
30
31 #import "CoreGraphicsSPI.h"
32 #import "GeometryUtilities.h"
33 #import "GraphicsContext.h"
34 #import "PathUtilities.h"
35 #import "QuartzCoreSPI.h"
36 #import "TextIndicator.h"
37 #import "WebActionDisablingCALayerDelegate.h"
38
39 const CFTimeInterval bounceAnimationDuration = 0.12;
40 const CFTimeInterval bounceWithCrossfadeAnimationDuration = 0.3;
41 const CFTimeInterval fadeInAnimationDuration = 0.15;
42 const CFTimeInterval timeBeforeFadeStarts = bounceAnimationDuration + 0.2;
43 const CFTimeInterval fadeOutAnimationDuration = 0.3;
44
45 const CGFloat midBounceScale = 1.25;
46 const CGFloat borderWidth = 0;
47 const CGFloat cornerRadius = 0;
48 const CGFloat dropShadowOffsetX = 0;
49 const CGFloat dropShadowOffsetY = 1;
50 const CGFloat dropShadowBlurRadius = 2;
51 const CGFloat rimShadowBlurRadius = 1;
52
53 NSString *textLayerKey = @"TextLayer";
54 NSString *dropShadowLayerKey = @"DropShadowLayer";
55 NSString *rimShadowLayerKey = @"RimShadowLayer";
56
57 using namespace WebCore;
58
59 @interface WebTextIndicatorView : NSView {
60     RefPtr<TextIndicator> _textIndicator;
61     RetainPtr<NSArray> _bounceLayers;
62     NSSize _margin;
63     bool _hasCompletedAnimation;
64     BOOL _fadingOut;
65 }
66
67 - (instancetype)initWithFrame:(NSRect)frame textIndicator:(PassRefPtr<TextIndicator>)textIndicator margin:(NSSize)margin offset:(NSPoint)offset;
68
69 - (void)present;
70 - (void)hideWithCompletionHandler:(void(^)(void))completionHandler;
71
72 - (void)setAnimationProgress:(float)progress;
73 - (BOOL)hasCompletedAnimation;
74
75 @property (nonatomic, getter=isFadingOut) BOOL fadingOut;
76
77 @end
78
79 @implementation WebTextIndicatorView
80
81 @synthesize fadingOut = _fadingOut;
82
83 static bool indicatorWantsBounce(const TextIndicator& indicator)
84 {
85     switch (indicator.presentationTransition()) {
86     case TextIndicatorPresentationTransition::BounceAndCrossfade:
87     case TextIndicatorPresentationTransition::Bounce:
88         return true;
89
90     case TextIndicatorPresentationTransition::FadeIn:
91     case TextIndicatorPresentationTransition::None:
92         return false;
93     }
94
95     ASSERT_NOT_REACHED();
96     return false;
97 }
98
99 static bool indicatorWantsContentCrossfade(const TextIndicator& indicator)
100 {
101     if (!indicator.data().contentImageWithHighlight)
102         return false;
103
104     switch (indicator.presentationTransition()) {
105     case TextIndicatorPresentationTransition::BounceAndCrossfade:
106         return true;
107
108     case TextIndicatorPresentationTransition::Bounce:
109     case TextIndicatorPresentationTransition::FadeIn:
110     case TextIndicatorPresentationTransition::None:
111         return false;
112     }
113
114     ASSERT_NOT_REACHED();
115     return false;
116 }
117
118 static bool indicatorWantsFadeIn(const TextIndicator& indicator)
119 {
120     switch (indicator.presentationTransition()) {
121     case TextIndicatorPresentationTransition::FadeIn:
122         return true;
123
124     case TextIndicatorPresentationTransition::Bounce:
125     case TextIndicatorPresentationTransition::BounceAndCrossfade:
126     case TextIndicatorPresentationTransition::None:
127         return false;
128     }
129
130     ASSERT_NOT_REACHED();
131     return false;
132 }
133
134 static bool indicatorWantsManualAnimation(const TextIndicator& indicator)
135 {
136     switch (indicator.presentationTransition()) {
137     case TextIndicatorPresentationTransition::FadeIn:
138         return true;
139
140     case TextIndicatorPresentationTransition::Bounce:
141     case TextIndicatorPresentationTransition::BounceAndCrossfade:
142     case TextIndicatorPresentationTransition::None:
143         return false;
144     }
145
146     ASSERT_NOT_REACHED();
147     return false;
148 }
149
150 - (instancetype)initWithFrame:(NSRect)frame textIndicator:(PassRefPtr<TextIndicator>)textIndicator margin:(NSSize)margin offset:(NSPoint)offset
151 {
152     if (!(self = [super initWithFrame:frame]))
153         return nil;
154
155     _textIndicator = textIndicator;
156     _margin = margin;
157
158     self.wantsLayer = YES;
159     self.layer.anchorPoint = CGPointZero;
160
161     FloatSize contentsImageLogicalSize = _textIndicator->contentImage()->size();
162     contentsImageLogicalSize.scale(1 / _textIndicator->contentImageScaleFactor());
163     RetainPtr<CGImageRef> contentsImage;
164     if (indicatorWantsContentCrossfade(*_textIndicator))
165         contentsImage = _textIndicator->contentImageWithHighlight()->getCGImageRef();
166     else
167         contentsImage = _textIndicator->contentImage()->getCGImageRef();
168
169     RetainPtr<NSMutableArray> bounceLayers = adoptNS([[NSMutableArray alloc] init]);
170
171     RetainPtr<CGColorRef> highlightColor = [NSColor colorWithDeviceRed:1 green:1 blue:0 alpha:1].CGColor;
172     RetainPtr<CGColorRef> rimShadowColor = [NSColor colorWithDeviceWhite:0 alpha:0.35].CGColor;
173     RetainPtr<CGColorRef> dropShadowColor = [NSColor colorWithDeviceWhite:0 alpha:0.2].CGColor;
174
175     RetainPtr<CGColorRef> borderColor = [NSColor colorWithDeviceRed:.96 green:.90 blue:0 alpha:1].CGColor;
176
177     Vector<FloatRect> textRectsInBoundingRectCoordinates = _textIndicator->textRectsInBoundingRectCoordinates();
178
179     Vector<Path> paths = PathUtilities::pathsWithShrinkWrappedRects(textRectsInBoundingRectCoordinates, cornerRadius);
180
181     for (const auto& path : paths) {
182         FloatRect pathBoundingRect = path.boundingRect();
183
184         Path translatedPath;
185         AffineTransform transform;
186         transform.translate(-pathBoundingRect.x(), -pathBoundingRect.y());
187         translatedPath.addPath(path, transform);
188
189         FloatRect offsetTextRect = pathBoundingRect;
190         offsetTextRect.move(offset.x, offset.y);
191
192         FloatRect bounceLayerRect = offsetTextRect;
193         bounceLayerRect.move(_margin.width, _margin.height);
194
195         RetainPtr<CALayer> bounceLayer = adoptNS([[CALayer alloc] init]);
196         [bounceLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
197         [bounceLayer setFrame:bounceLayerRect];
198         [bounceLayer setOpacity:0];
199         [bounceLayers addObject:bounceLayer.get()];
200
201         FloatRect yellowHighlightRect(FloatPoint(), bounceLayerRect.size());
202
203         RetainPtr<CALayer> dropShadowLayer = adoptNS([[CALayer alloc] init]);
204         [dropShadowLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
205         [dropShadowLayer setShadowColor:dropShadowColor.get()];
206         [dropShadowLayer setShadowRadius:dropShadowBlurRadius];
207         [dropShadowLayer setShadowOffset:CGSizeMake(dropShadowOffsetX, dropShadowOffsetY)];
208         [dropShadowLayer setShadowPath:translatedPath.platformPath()];
209         [dropShadowLayer setShadowOpacity:1];
210         [dropShadowLayer setFrame:yellowHighlightRect];
211         [bounceLayer addSublayer:dropShadowLayer.get()];
212         [bounceLayer setValue:dropShadowLayer.get() forKey:dropShadowLayerKey];
213
214         RetainPtr<CALayer> rimShadowLayer = adoptNS([[CALayer alloc] init]);
215         [rimShadowLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
216         [rimShadowLayer setFrame:yellowHighlightRect];
217         [rimShadowLayer setShadowColor:rimShadowColor.get()];
218         [rimShadowLayer setShadowRadius:rimShadowBlurRadius];
219         [rimShadowLayer setShadowPath:translatedPath.platformPath()];
220         [rimShadowLayer setShadowOffset:CGSizeZero];
221         [rimShadowLayer setShadowOpacity:1];
222         [rimShadowLayer setFrame:yellowHighlightRect];
223         [bounceLayer addSublayer:rimShadowLayer.get()];
224         [bounceLayer setValue:rimShadowLayer.get() forKey:rimShadowLayerKey];
225
226         RetainPtr<CALayer> textLayer = adoptNS([[CALayer alloc] init]);
227         [textLayer setBackgroundColor:highlightColor.get()];
228         [textLayer setBorderColor:borderColor.get()];
229         [textLayer setBorderWidth:borderWidth];
230         [textLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
231         [textLayer setContents:(id)contentsImage.get()];
232
233         RetainPtr<CAShapeLayer> maskLayer = adoptNS([[CAShapeLayer alloc] init]);
234         [maskLayer setPath:translatedPath.platformPath()];
235         [textLayer setMask:maskLayer.get()];
236
237         FloatRect imageRect = pathBoundingRect;
238         [textLayer setContentsRect:CGRectMake(imageRect.x() / contentsImageLogicalSize.width(), imageRect.y() / contentsImageLogicalSize.height(), imageRect.width() / contentsImageLogicalSize.width(), imageRect.height() / contentsImageLogicalSize.height())];
239         [textLayer setContentsGravity:kCAGravityCenter];
240         [textLayer setContentsScale:_textIndicator->contentImageScaleFactor()];
241         [textLayer setFrame:yellowHighlightRect];
242         [bounceLayer setValue:textLayer.get() forKey:textLayerKey];
243         [bounceLayer addSublayer:textLayer.get()];
244     }
245
246     self.layer.sublayers = bounceLayers.get();
247     _bounceLayers = bounceLayers;
248
249     return self;
250 }
251
252 static RetainPtr<CAKeyframeAnimation> createBounceAnimation(CFTimeInterval duration)
253 {
254     RetainPtr<CAKeyframeAnimation> bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
255     [bounceAnimation setValues:@[
256         [NSValue valueWithCATransform3D:CATransform3DIdentity],
257         [NSValue valueWithCATransform3D:CATransform3DMakeScale(midBounceScale, midBounceScale, 1)],
258         [NSValue valueWithCATransform3D:CATransform3DIdentity]
259         ]];
260     [bounceAnimation setDuration:duration];
261
262     return bounceAnimation;
263 }
264
265 static RetainPtr<CABasicAnimation> createContentCrossfadeAnimation(CFTimeInterval duration, TextIndicator& textIndicator)
266 {
267     RetainPtr<CABasicAnimation> crossfadeAnimation = [CABasicAnimation animationWithKeyPath:@"contents"];
268     RetainPtr<CGImageRef> contentsImage = textIndicator.contentImage()->getCGImageRef();
269     [crossfadeAnimation setToValue:(id)contentsImage.get()];
270     [crossfadeAnimation setFillMode:kCAFillModeForwards];
271     [crossfadeAnimation setRemovedOnCompletion:NO];
272     [crossfadeAnimation setDuration:duration];
273
274     return crossfadeAnimation;
275 }
276
277 static RetainPtr<CABasicAnimation> createShadowFadeAnimation(CFTimeInterval duration)
278 {
279     RetainPtr<CABasicAnimation> fadeShadowInAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
280     [fadeShadowInAnimation setFromValue:@0];
281     [fadeShadowInAnimation setToValue:@1];
282     [fadeShadowInAnimation setFillMode:kCAFillModeForwards];
283     [fadeShadowInAnimation setRemovedOnCompletion:NO];
284     [fadeShadowInAnimation setDuration:duration];
285
286     return fadeShadowInAnimation;
287 }
288
289 static RetainPtr<CABasicAnimation> createFadeInAnimation(CFTimeInterval duration)
290 {
291     RetainPtr<CABasicAnimation> fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
292     [fadeInAnimation setFromValue:@0];
293     [fadeInAnimation setToValue:@1];
294     [fadeInAnimation setFillMode:kCAFillModeForwards];
295     [fadeInAnimation setRemovedOnCompletion:NO];
296     [fadeInAnimation setDuration:duration];
297
298     return fadeInAnimation;
299 }
300
301 - (CFTimeInterval)_animationDuration
302 {
303     if (indicatorWantsBounce(*_textIndicator)) {
304         if (indicatorWantsContentCrossfade(*_textIndicator))
305             return bounceWithCrossfadeAnimationDuration;
306         return bounceAnimationDuration;
307     }
308
309     return fadeInAnimationDuration;
310 }
311
312 - (BOOL)hasCompletedAnimation
313 {
314     return _hasCompletedAnimation;
315 }
316
317 - (void)present
318 {
319     bool wantsBounce = indicatorWantsBounce(*_textIndicator);
320     bool wantsCrossfade = indicatorWantsContentCrossfade(*_textIndicator);
321     bool wantsFadeIn = indicatorWantsFadeIn(*_textIndicator);
322     CFTimeInterval animationDuration = [self _animationDuration];
323
324     _hasCompletedAnimation = false;
325
326     RetainPtr<CAAnimation> presentationAnimation;
327     if (wantsBounce)
328         presentationAnimation = createBounceAnimation(animationDuration);
329     else if (wantsFadeIn)
330         presentationAnimation = createFadeInAnimation(animationDuration);
331
332     RetainPtr<CABasicAnimation> crossfadeAnimation;
333     RetainPtr<CABasicAnimation> fadeShadowInAnimation;
334     if (wantsCrossfade) {
335         crossfadeAnimation = createContentCrossfadeAnimation(animationDuration, *_textIndicator);
336         fadeShadowInAnimation = createShadowFadeAnimation(animationDuration);
337     }
338
339     [CATransaction begin];
340     for (CALayer *bounceLayer in _bounceLayers.get()) {
341         if (indicatorWantsManualAnimation(*_textIndicator))
342             bounceLayer.speed = 0;
343
344         if (!wantsFadeIn)
345             bounceLayer.opacity = 1;
346
347         if (presentationAnimation)
348             [bounceLayer addAnimation:presentationAnimation.get() forKey:@"presentation"];
349
350         if (wantsCrossfade) {
351             [[bounceLayer valueForKey:textLayerKey] addAnimation:crossfadeAnimation.get() forKey:@"contentTransition"];
352             [[bounceLayer valueForKey:dropShadowLayerKey] addAnimation:fadeShadowInAnimation.get() forKey:@"fadeShadowIn"];
353             [[bounceLayer valueForKey:rimShadowLayerKey] addAnimation:fadeShadowInAnimation.get() forKey:@"fadeShadowIn"];
354         }
355     }
356     [CATransaction commit];
357 }
358
359 - (void)hideWithCompletionHandler:(void(^)(void))completionHandler
360 {
361     RetainPtr<CABasicAnimation> fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
362     [fadeAnimation setFromValue:@1];
363     [fadeAnimation setToValue:@0];
364     [fadeAnimation setFillMode:kCAFillModeForwards];
365     [fadeAnimation setRemovedOnCompletion:NO];
366     [fadeAnimation setDuration:fadeOutAnimationDuration];
367
368     [CATransaction begin];
369     [CATransaction setCompletionBlock:completionHandler];
370     [self.layer addAnimation:fadeAnimation.get() forKey:@"fadeOut"];
371     [CATransaction commit];
372 }
373
374 - (void)setAnimationProgress:(float)progress
375 {
376     if (_hasCompletedAnimation)
377         return;
378
379     if (progress == 1) {
380         _hasCompletedAnimation = true;
381
382         for (CALayer *bounceLayer in _bounceLayers.get()) {
383             // Continue the animation from wherever it had manually progressed to.
384             CFTimeInterval beginTime = bounceLayer.timeOffset;
385             bounceLayer.speed = 1;
386             beginTime = [bounceLayer convertTime:CACurrentMediaTime() fromLayer:nil] - beginTime;
387             bounceLayer.beginTime = beginTime;
388         }
389     } else {
390         CFTimeInterval animationDuration = [self _animationDuration];
391         for (CALayer *bounceLayer in _bounceLayers.get())
392             bounceLayer.timeOffset = progress * animationDuration;
393     }
394 }
395
396 - (BOOL)isFlipped
397 {
398     return YES;
399 }
400
401 @end
402
403 namespace WebCore {
404
405 TextIndicatorWindow::TextIndicatorWindow(NSView *targetView)
406     : m_targetView(targetView)
407     , m_temporaryTextIndicatorTimer(RunLoop::main(), this, &TextIndicatorWindow::startFadeOut)
408 {
409 }
410
411 TextIndicatorWindow::~TextIndicatorWindow()
412 {
413     clearTextIndicator(TextIndicatorWindowDismissalAnimation::FadeOut);
414 }
415
416 void TextIndicatorWindow::setAnimationProgress(float progress)
417 {
418     if (!m_textIndicator)
419         return;
420
421     [m_textIndicatorView setAnimationProgress:progress];
422 }
423
424 void TextIndicatorWindow::clearTextIndicator(TextIndicatorWindowDismissalAnimation animation)
425 {
426     RefPtr<TextIndicator> textIndicator = WTFMove(m_textIndicator);
427
428     if ([m_textIndicatorView isFadingOut])
429         return;
430
431     if (textIndicator && indicatorWantsManualAnimation(*textIndicator) && [m_textIndicatorView hasCompletedAnimation] && animation == TextIndicatorWindowDismissalAnimation::FadeOut) {
432         startFadeOut();
433         return;
434     }
435
436     closeWindow();
437 }
438
439 void TextIndicatorWindow::setTextIndicator(Ref<TextIndicator> textIndicator, CGRect textBoundingRectInScreenCoordinates, TextIndicatorWindowLifetime lifetime)
440 {
441     if (m_textIndicator == textIndicator.ptr())
442         return;
443
444     closeWindow();
445
446     m_textIndicator = textIndicator.ptr();
447
448     CGFloat horizontalMargin = dropShadowBlurRadius * 2 + TextIndicator::defaultHorizontalMargin;
449     CGFloat verticalMargin = dropShadowBlurRadius * 2 + TextIndicator::defaultVerticalMargin;
450     
451     if (indicatorWantsBounce(*m_textIndicator)) {
452         horizontalMargin = std::max(horizontalMargin, textBoundingRectInScreenCoordinates.size.width * (midBounceScale - 1) + horizontalMargin);
453         verticalMargin = std::max(verticalMargin, textBoundingRectInScreenCoordinates.size.height * (midBounceScale - 1) + verticalMargin);
454     }
455
456     horizontalMargin = CGCeiling(horizontalMargin);
457     verticalMargin = CGCeiling(verticalMargin);
458
459     CGRect contentRect = CGRectInset(textBoundingRectInScreenCoordinates, -horizontalMargin, -verticalMargin);
460     NSRect windowContentRect = [NSWindow contentRectForFrameRect:NSRectFromCGRect(contentRect) styleMask:NSBorderlessWindowMask];
461     NSRect integralWindowContentRect = NSIntegralRect(windowContentRect);
462     NSPoint fractionalTextOffset = NSMakePoint(windowContentRect.origin.x - integralWindowContentRect.origin.x, windowContentRect.origin.y - integralWindowContentRect.origin.y);
463     m_textIndicatorWindow = adoptNS([[NSWindow alloc] initWithContentRect:integralWindowContentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]);
464
465     [m_textIndicatorWindow setBackgroundColor:[NSColor clearColor]];
466     [m_textIndicatorWindow setOpaque:NO];
467     [m_textIndicatorWindow setIgnoresMouseEvents:YES];
468
469     m_textIndicatorView = adoptNS([[WebTextIndicatorView alloc] initWithFrame:NSMakeRect(0, 0, [m_textIndicatorWindow frame].size.width, [m_textIndicatorWindow frame].size.height) textIndicator:m_textIndicator margin:NSMakeSize(horizontalMargin, verticalMargin) offset:fractionalTextOffset]);
470     [m_textIndicatorWindow setContentView:m_textIndicatorView.get()];
471
472     [[m_targetView window] addChildWindow:m_textIndicatorWindow.get() ordered:NSWindowAbove];
473     [m_textIndicatorWindow setReleasedWhenClosed:NO];
474
475     if (m_textIndicator->presentationTransition() != TextIndicatorPresentationTransition::None)
476         [m_textIndicatorView present];
477
478     if (lifetime == TextIndicatorWindowLifetime::Temporary)
479         m_temporaryTextIndicatorTimer.startOneShot(timeBeforeFadeStarts);
480 }
481
482 void TextIndicatorWindow::closeWindow()
483 {
484     if (!m_textIndicatorWindow)
485         return;
486
487     if ([m_textIndicatorView isFadingOut])
488         return;
489
490     m_temporaryTextIndicatorTimer.stop();
491
492     [[m_textIndicatorWindow parentWindow] removeChildWindow:m_textIndicatorWindow.get()];
493     [m_textIndicatorWindow close];
494     m_textIndicatorWindow = nullptr;
495 }
496
497 void TextIndicatorWindow::startFadeOut()
498 {
499     [m_textIndicatorView setFadingOut:YES];
500     RetainPtr<NSWindow> indicatorWindow = m_textIndicatorWindow;
501     [m_textIndicatorView hideWithCompletionHandler:[indicatorWindow] {
502         [[indicatorWindow parentWindow] removeChildWindow:indicatorWindow.get()];
503         [indicatorWindow close];
504     }];
505 }
506
507 } // namespace WebCore
508
509 #endif // PLATFORM(MAC)