1
/*
2
    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
3
4
    This library is free software; you can redistribute it and/or
5
    modify it under the terms of the GNU Library General Public
6
    License as published by the Free Software Foundation; either
7
    version 2 of the License, or (at your option) any later version.
8
9
    This library is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
    Library General Public License for more details.
13
14
    You should have received a copy of the GNU Library General Public License
15
    along with this library; see the file COPYING.LIB.  If not, write to
16
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
    Boston, MA 02110-1301, USA.
18
*/
19
20
#include "config.h"
21
#include "GraphicsLayerQt.h"
22
23
#include "CurrentTime.h"
24
#include "FloatRect.h"
25
#include "GraphicsContext.h"
26
#include "Image.h"
27
#include "RefCounted.h"
28
#include "TranslateTransformOperation.h"
29
#include "UnitBezier.h"
30
#include <QtCore/qabstractanimation.h>
31
#include <QtCore/qdebug.h>
32
#include <QtCore/qset.h>
33
#include <QtCore/qtimer.h>
34
#include <QtGui/qbitmap.h>
35
#include <QtGui/qcolor.h>
36
#include <QtGui/qgraphicseffect.h>
37
#include <QtGui/qgraphicsitem.h>
38
#include <QtGui/qgraphicsscene.h>
39
#include <QtGui/qmatrix4x4.h>
40
#include <QtGui/qpainter.h>
41
#include <QtGui/qpalette.h>
42
#include <QtGui/qpixmap.h>
43
#include <QtGui/qstyleoption.h>
44
45
namespace WebCore {
46
47
class GraphicsLayerQtImpl : public QGraphicsObject {
48
    Q_OBJECT
49
50
public:
51
    // this set of flags help us defer which properties of the layer have been
52
    // modified by the compositor, so we can know what to look for in the next flush
53
    enum ChangeFlag {
54
        NoChanges =                 0,
55
        ChildrenChange =            (1L << 1),
56
        MaskLayerChange =           (1L << 2),
57
        PositionChange =            (1L << 3),
58
        AnchorPointChange =         (1L << 4),
59
        SizeChange  =               (1L << 5),
60
        TransformChange =           (1L << 6),
61
        ContentChange =             (1L << 7),
62
        GeometryOrientationChange = (1L << 8),
63
        ContentsOrientationChange = (1L << 9),
64
        OpacityChange =             (1L << 10),
65
        ContentsRectChange =        (1L << 11),
66
        Preserves3DChange =         (1L << 12),
67
        MasksToBoundsChange =       (1L << 13),
68
        DrawsContentChange =        (1L << 14),
69
        ContentsOpaqueChange =      (1L << 15),
70
        BackfaceVisibilityChange =  (1L << 16),
71
        ChildrenTransformChange =   (1L << 17),
72
        DisplayChange =             (1L << 18),
73
        BackgroundColorChange =     (1L << 19),
74
        ParentChange =              (1L << 20),
75
        DistributesOpacityChange =  (1L << 21)
76
    };
77
78
    // the compositor lets us special-case images and colors, so we try to do so
79
    enum StaticContentType { HTMLContentType, PixmapContentType, ColorContentType};
80
81
    GraphicsLayerQtImpl(GraphicsLayerQt* newLayer);
82
    virtual ~GraphicsLayerQtImpl();
83
84
    // reimps from QGraphicsItem
85
    virtual QPainterPath opaqueArea() const;
86
    virtual QRectF boundingRect() const;
87
    virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget);
88
89
    // we manage transforms ourselves because transform-origin acts differently in webkit and in Qt
90
    void setBaseTransform(const QTransform& t);
91
    void drawContents(QPainter* painter, const QRectF& r, bool mask = false);
92
93
    // let the compositor-API tell us which properties were changed
94
    void notifyChange(ChangeFlag changeFlag);
95
96
    // called when the compositor is ready for us to show the changes on screen
97
    // this is called indirectly from ChromeClientQt::setNeedsOneShotDrawingSynchronization
98
    // (meaning the sync would happen together with the next draw)
99
    // or ChromeClientQt::scheduleCompositingLayerSync (meaning the sync will happen ASAP)
100
    void flushChanges(bool recursive = true);
101
102
public slots:
103
    // we need to notify the client (aka the layer compositor) when the animation actually starts
104
    void notifyAnimationStarted();
105
106
public:
107
    GraphicsLayerQt* m_layer;
108
109
    QTransform m_baseTransfom;
110
    bool m_transformAnimationRunning;
111
    bool opacityAnimationRunning;
112
113
    struct ContentData {
114
        QPixmap pixmap;
115
        QRegion regionToUpdate;
116
        bool updateAll;
117
        QColor contentsBackgroundColor;
118
        QColor backgroundColor;
119
        StaticContentType contentType;
120
        float opacity;
121
        ContentData()
122
                : updateAll(false)
123
                , backgroundColor(QColor())
124
                , contentType(HTMLContentType)
125
                , opacity(1)
126
        {
127
        }
128
129
    };
130
131
    ContentData m_pendingContent;
132
    ContentData m_currentContent;
133
134
    ChangeFlag m_changeMask;
135
136
    QSizeF m_size;
137
    QList<QWeakPointer<QAbstractAnimation> > m_animations;
138
    QTimer m_suspendTimer;
139
140
    struct State {
141
        GraphicsLayer* maskLayer;
142
        FloatPoint pos;
143
        FloatPoint3D anchorPoint;
144
        FloatSize size;
145
        TransformationMatrix transform;
146
        TransformationMatrix childrenTransform;
147
        Color backgroundColor;
148
        Color currentColor;
149
        GraphicsLayer::CompositingCoordinatesOrientation geoOrientation;
150
        GraphicsLayer::CompositingCoordinatesOrientation contentsOrientation;
151
        float opacity;
152
        QRect contentsRect;
153
154
        bool preserves3D: 1;
155
        bool masksToBounds: 1;
156
        bool drawsContent: 1;
157
        bool contentsOpaque: 1;
158
        bool backfaceVisibility: 1;
159
        bool distributeOpacity: 1;
160
        bool align: 2;
161
        State() : maskLayer(0), opacity(1), preserves3D(false), masksToBounds(false),
162
                  drawsContent(false), contentsOpaque(false), backfaceVisibility(false),
163
                  distributeOpacity(false)
164
        {
165
        }
166
    } m_state;
167
};
168
169
GraphicsLayerQtImpl::GraphicsLayerQtImpl(GraphicsLayerQt* newLayer)
170
        : QGraphicsObject(0)
171
        , m_layer(newLayer)
172
        , m_transformAnimationRunning(false)
173
        , m_changeMask(NoChanges)
174
175
{
176
    // better to calculate the exposed rect in QGraphicsView than over-render in WebCore
177
    // FIXME: test different approaches
178
    setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true);
179
180
    // we use graphics-view for compositing, not for interactivity
181
    setAcceptedMouseButtons(Qt::NoButton);
182
    setEnabled(false);
183
184
    // we'll set the cache when we know what's going on
185
    setCacheMode(NoCache);
186
}
187
188
GraphicsLayerQtImpl::~GraphicsLayerQtImpl()
189
{
190
    // the compositor manages item lifecycle - we don't want the graphics-view
191
    // system to automatically delete our items
192
193
    const QList<QGraphicsItem*> children = childItems();
194
    for (QList<QGraphicsItem*>::const_iterator it = children.begin(); it != children.end(); ++it) {
195
        if (QGraphicsItem* item = *it) {
196
            if (scene())
197
                scene()->removeItem(item);
198
            item->setParentItem(0);
199
        }
200
    }
201
    
202
    // we do, however, own the animations...
203
    for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_animations.begin(); it != m_animations.end(); ++it)
204
        if (QAbstractAnimation* anim = it->data())
205
            delete anim;
206
}
207
208
void GraphicsLayerQtImpl::setBaseTransform(const QTransform& transform)
209
{
210
    if (!m_layer)
211
        return;
212
    // webkit has relative-to-size originPoint, graphics-view has a pixel originPoint
213
    // here we convert
214
    QPointF originTranslate(
215
            m_layer->anchorPoint().x() * m_layer->size().width(), m_layer->anchorPoint().y() * m_layer->size().height());
216
217
    resetTransform();
218
219
    // we have to manage this ourselves because QGraphicsView's transformOrigin is incomplete
220
    translate(originTranslate.x(), originTranslate.y());
221
    setTransform(transform, true);
222
    translate(-originTranslate.x(), -originTranslate.y());
223
    m_baseTransfom = transform;
224
}
225
226
QPainterPath GraphicsLayerQtImpl::opaqueArea() const
227
{
228
    QPainterPath painterPath;
229
    // we try out best to return the opaque area, maybe it will help graphics-view render less items
230
    if (m_currentContent.backgroundColor.isValid() && m_currentContent.backgroundColor.alpha() == 0xff)
231
        painterPath.addRect(boundingRect());
232
    else {
233
        if (m_state.contentsOpaque
234
            || (m_currentContent.contentType == ColorContentType && m_currentContent.contentsBackgroundColor.alpha() == 0xff)
235
            || (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlpha())) {
236
237
            painterPath.addRect(m_state.contentsRect);
238
        }
239
    }
240
    return painterPath;
241
}
242
243
QRectF GraphicsLayerQtImpl::boundingRect() const
244
{
245
    return QRectF(QPointF(0, 0), QSizeF(m_size));
246
}
247
248
void GraphicsLayerQtImpl::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
249
{
250
    if (m_state.maskLayer && m_state.maskLayer->platformLayer()) {
251
        // FIXME: see if this is better done somewhere else
252
        GraphicsLayerQtImpl* otherMask = static_cast<GraphicsLayerQtImpl*>(m_state.maskLayer->platformLayer());
253
        otherMask->flushChanges(true);
254
        
255
        // CSS3 mask and QGraphicsOpacityEffect are the same thing! we just need to convert...
256
        // The conversion is as fast as we can make it - we render the layer once and send it to the QGraphicsOpacityEffect
257
        if (!graphicsEffect()) {
258
            QPixmap mask(QSize(m_state.maskLayer->size().width(), m_state.maskLayer->size().height()));
259
            mask.fill(Qt::transparent);
260
            {
261
                QPainter p(&mask);
262
                p.setRenderHints(painter->renderHints(), true);
263
                p.setCompositionMode(QPainter::CompositionMode_Source);
264
                static_cast<GraphicsLayerQtImpl*>(m_state.maskLayer->platformLayer())->drawContents(&p, option->exposedRect, true);
265
            }
266
            QGraphicsOpacityEffect* opacityEffect = new QGraphicsOpacityEffect(this);
267
            opacityEffect->setOpacity(1);
268
            opacityEffect->setOpacityMask(QBrush(mask));
269
            setGraphicsEffect(opacityEffect);
270
        }
271
    }
272
    drawContents(painter, option->exposedRect);
273
}
274
275
void GraphicsLayerQtImpl::drawContents(QPainter* painter, const QRectF& r, bool mask)
276
{
277
    QRect rect = r.toAlignedRect();
278
    
279
    if (m_currentContent.contentType != HTMLContentType && !m_state.contentsRect.isEmpty())
280
        rect = rect.intersected(m_state.contentsRect);
281
282
    if (m_currentContent.backgroundColor.isValid())
283
        painter->fillRect(r, QColor(m_currentContent.backgroundColor));
284
285
    if (!rect.isEmpty()) {
286
        switch (m_currentContent.contentType) {
287
        case PixmapContentType:
288
            // we have to scale the image to the contentsRect
289
            // FIXME: a better way would probably be drawPixmap with a src/target rect
290
            painter->drawPixmap(rect.topLeft(), m_currentContent.pixmap.scaled(m_state.contentsRect.size()), r);
291
            break;
292
        case ColorContentType:
293
            painter->fillRect(rect, m_currentContent.contentsBackgroundColor);
294
            break;
295
        default:
296
            if (m_state.drawsContent) {
297
                // this is the "expensive" bit. we try to minimize calls to this
298
                // neck of the woods by proper caching
299
                GraphicsContext gc(painter);
300
                m_layer->paintGraphicsLayerContents(gc, rect);
301
            }
302
            break;
303
        }
304
    }
305
}
306
307
void GraphicsLayerQtImpl::notifyChange(ChangeFlag changeFlag)
308
{
309
    if (!this)
310
        return;
311
312
    m_changeMask = static_cast<ChangeFlag>(static_cast<int>(m_changeMask) | static_cast<int>(changeFlag));
313
314
    if (m_layer->client())
315
        m_layer->client()->notifySyncRequired(m_layer);
316
}
317
318
void GraphicsLayerQtImpl::flushChanges(bool recursive)
319
{
320
    // this is the bulk of the work. understanding what the compositor is trying to achieve,
321
    // what graphics-view can do, and trying to find a sane common-grounds
322
    if (!m_layer || m_changeMask == NoChanges)
323
        goto afterLayerChanges;
324
325
    if (m_currentContent.contentType == HTMLContentType && (m_changeMask & ParentChange)) {
326
        // the WebCore compositor manages item ownership. We have to make sure
327
        // graphics-view doesn't try to snatch that ownership...
328
        if (!m_layer->parent() && !parentItem())
329
            setParentItem(0);
330
        else if (m_layer && m_layer->parent() && m_layer->parent()->nativeLayer() != parentItem())
331
            setParentItem(m_layer->parent()->nativeLayer());
332
    }
333
334
    if (m_changeMask & ChildrenChange) {
335
        // we basically do an XOR operation on the list of current children
336
        // and the list of wanted children, and remove/add
337
        QSet<QGraphicsItem*> newChildren;
338
        const Vector<GraphicsLayer*> newChildrenVector = (m_layer->children());
339
        newChildren.reserve(newChildrenVector.size());
340
341
        for (size_t i = 0; i < newChildrenVector.size(); ++i)
342
            newChildren.insert(newChildrenVector[i]->platformLayer());
343
344
        const QSet<QGraphicsItem*> currentChildren = childItems().toSet();
345
        const QSet<QGraphicsItem*> childrenToAdd = newChildren - currentChildren;
346
        const QSet<QGraphicsItem*> childrenToRemove = currentChildren - newChildren;
347
        for (QSet<QGraphicsItem*>::const_iterator it = childrenToAdd.begin(); it != childrenToAdd.end(); ++it) {
348
             if (QGraphicsItem* w = *it)
349
                w->setParentItem(this);
350
        }
351
        for (QSet<QGraphicsItem*>::const_iterator it = childrenToRemove.begin(); it != childrenToRemove.end(); ++it) {
352
             if (QGraphicsItem* w = *it)
353
                w->setParentItem(0);
354
        }
355
356
        // children are ordered by z-value, let graphics-view know.
357
        for (size_t i = 0; i < newChildrenVector.size(); ++i)
358
            if (newChildrenVector[i]->platformLayer())
359
                newChildrenVector[i]->platformLayer()->setZValue(i);
360
    }
361
362
    if (m_changeMask & MaskLayerChange) {
363
        // we can't paint here, because we don't know if the mask layer
364
        // itself is ready... we'll have to wait till this layer tries to paint
365
        setGraphicsEffect(0);
366
        if (m_layer->maskLayer())
367
            setFlag(ItemClipsChildrenToShape, true);
368
        else
369
            setFlag(ItemClipsChildrenToShape, m_layer->masksToBounds());
370
        update();
371
    }
372
373
    if ((m_changeMask & PositionChange) && (m_layer->position() != m_state.pos))
374
        setPos(m_layer->position().x(), m_layer->position().y());
375
376
    if (m_changeMask & SizeChange) {
377
        if (m_layer->size() != m_state.size) {
378
            prepareGeometryChange();
379
            m_size = QSizeF(m_layer->size().width(), m_layer->size().height());
380
        }
381
    }
382
383
    if (m_changeMask & (TransformChange | AnchorPointChange | SizeChange)) {
384
        // since we convert a percentage-based origin-point to a pixel-based one,
385
        // the anchor-point, transform and size from WebCore all affect the one
386
        // that we give Qt
387
        if (m_state.transform != m_layer->transform() || m_state.anchorPoint != m_layer->anchorPoint() || m_state.size != m_layer->size())
388
            setBaseTransform(QTransform(m_layer->transform()));
389
    }
390
391
    if (m_changeMask & (ContentChange | DrawsContentChange)) {
392
        switch (m_pendingContent.contentType) {
393
        case PixmapContentType:
394
            // we need cache even for images, because they need to be resized
395
            // to the contents rect. maybe this can be optimized though
396
            setCacheMode(m_transformAnimationRunning ? ItemCoordinateCache : DeviceCoordinateCache);
397
            update();
398
            setFlag(ItemHasNoContents, false);
399
            break;
400
401
        case ColorContentType:
402
            // no point in caching a solid-color rectangle
403
            setCacheMode(QGraphicsItem::NoCache);
404
            if (m_pendingContent.contentType != m_currentContent.contentType || m_pendingContent.contentsBackgroundColor != m_currentContent.contentsBackgroundColor)
405
                update();
406
            m_state.drawsContent = false;
407
            setFlag(ItemHasNoContents, false);
408
            break;
409
410
        case HTMLContentType:
411
            if (m_pendingContent.contentType != m_currentContent.contentType)
412
                update();
413
            if (!m_state.drawsContent && m_layer->drawsContent())
414
                update();
415
            if (m_layer->drawsContent())
416
                setCacheMode(m_transformAnimationRunning ? ItemCoordinateCache : DeviceCoordinateCache);
417
            else
418
                setCacheMode(NoCache);
419
420
            setFlag(ItemHasNoContents, !m_layer->drawsContent());
421
            break;
422
        }
423
    }
424
425
    if ((m_changeMask & OpacityChange) && m_state.opacity != m_layer->opacity())
426
        setOpacity(m_layer->opacity());
427
428
    if (m_changeMask & ContentsRectChange) {
429
        const QRect rect(m_layer->contentsRect());
430
        if (m_state.contentsRect != rect) {
431
            m_state.contentsRect = rect;
432
            update();
433
        }
434
    }
435
436
    if ((m_changeMask & MasksToBoundsChange)
437
        && m_state.masksToBounds != m_layer->masksToBounds()) {
438
439
        setFlag(QGraphicsItem::ItemClipsToShape, m_layer->masksToBounds());
440
        setFlag(QGraphicsItem::ItemClipsChildrenToShape, m_layer->masksToBounds());
441
    }
442
443
    if ((m_changeMask & ContentsOpaqueChange) && m_state.contentsOpaque != m_layer->contentsOpaque())
444
        prepareGeometryChange();
445
446
    if (m_changeMask & DisplayChange)
447
        update(m_pendingContent.regionToUpdate.boundingRect());
448
449
    if ((m_changeMask & BackgroundColorChange) && (m_pendingContent.backgroundColor != m_currentContent.backgroundColor))
450
        update();
451
452
    // FIXME: the following flags are currently not handled, as they don't have a clear test or are in low priority
453
    // GeometryOrientationChange, ContentsOrientationChange, BackfaceVisibilityChange, ChildrenTransformChange
454
455
    m_state.maskLayer = m_layer->maskLayer();
456
    m_state.pos = m_layer->position();
457
    m_state.anchorPoint = m_layer->anchorPoint();
458
    m_state.size = m_layer->size();
459
    m_state.transform = m_layer->transform();
460
    m_state.geoOrientation = m_layer->geometryOrientation();
461
    m_state.contentsOrientation =m_layer->contentsOrientation();
462
    m_state.opacity = m_layer->opacity();
463
    m_state.contentsRect = m_layer->contentsRect();
464
    m_state.preserves3D = m_layer->preserves3D();
465
    m_state.masksToBounds = m_layer->masksToBounds();
466
    m_state.drawsContent = m_layer->drawsContent();
467
    m_state.contentsOpaque = m_layer->contentsOpaque();
468
    m_state.backfaceVisibility = m_layer->backfaceVisibility();
469
    m_currentContent.pixmap = m_pendingContent.pixmap;
470
    m_currentContent.contentType = m_pendingContent.contentType;
471
    m_currentContent.backgroundColor = m_pendingContent.backgroundColor;
472
    m_currentContent.regionToUpdate |= m_pendingContent.regionToUpdate;
473
    m_currentContent.contentsBackgroundColor = m_pendingContent.contentsBackgroundColor;
474
    m_pendingContent.regionToUpdate = QRegion();
475
    m_changeMask = NoChanges;
476
477
478
afterLayerChanges:
479
    if (!recursive)
480
        return;    
481
482
    const QList<QGraphicsItem*> children = childItems();
483
484
    for (QList<QGraphicsItem*>::const_iterator it = children.begin(); it != children.end(); ++it) {
485
        if (QGraphicsItem* item = *it)
486
            if (GraphicsLayerQtImpl* layer = qobject_cast<GraphicsLayerQtImpl*>(item->toGraphicsObject()))
487
                layer->flushChanges(true);
488
    }
489
}
490
491
void GraphicsLayerQtImpl::notifyAnimationStarted()
492
{
493
    // WebCore notifies javascript when the animation starts
494
    // here we're letting it know
495
    m_layer->client()->notifyAnimationStarted(m_layer, WTF::currentTime());
496
}
497
498
GraphicsLayerQt::GraphicsLayerQt(GraphicsLayerClient* client)
499
        : GraphicsLayer(client)
500
        , m_impl(PassOwnPtr<GraphicsLayerQtImpl>(new GraphicsLayerQtImpl(this)))
501
{
502
}
503
504
GraphicsLayerQt::~GraphicsLayerQt()
505
{
506
}
507
508
// this is the hook for WebCore compositor to know that Qt implements compositing with GraphicsLayerQt
509
PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client)
510
{
511
    return new GraphicsLayerQt(client);
512
}
513
514
// reimp from GraphicsLayer.h: Qt is top-down
515
GraphicsLayer::CompositingCoordinatesOrientation GraphicsLayer::compositingCoordinatesOrientation()
516
{
517
    return CompositingCoordinatesTopDown;
518
}
519
520
// reimp from GraphicsLayer.h: we'll need to update the whole display, and we can't count on the current size because it might change
521
void GraphicsLayerQt::setNeedsDisplay()
522
{
523
    m_impl->m_pendingContent.regionToUpdate = QRegion(QRect(QPoint(0, 0), QSize(size().width(), size().height())));
524
    m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange);
525
}
526
527
// reimp from GraphicsLayer.h
528
void GraphicsLayerQt::setNeedsDisplayInRect(const FloatRect& r)
529
{
530
    m_impl->m_pendingContent.regionToUpdate|= QRectF(r).toAlignedRect();
531
    m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange);
532
}
533
534
// reimp from GraphicsLayer.h
535
void GraphicsLayerQt::setName(const String& name)
536
{
537
    m_impl->setObjectName(name);
538
    GraphicsLayer::setName(name);
539
}
540
541
// reimp from GraphicsLayer.h
542
void GraphicsLayerQt::setParent(GraphicsLayer* layer)
543
{
544
    m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange);
545
    GraphicsLayer::setParent(layer);
546
}
547
548
// reimp from GraphicsLayer.h
549
bool GraphicsLayerQt::setChildren(const Vector<GraphicsLayer*>& children)
550
{
551
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
552
    return GraphicsLayer::setChildren(children);
553
}
554
555
// reimp from GraphicsLayer.h
556
void GraphicsLayerQt::addChild(GraphicsLayer* layer)
557
{
558
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
559
    GraphicsLayer::addChild(layer);
560
}
561
562
// reimp from GraphicsLayer.h
563
void GraphicsLayerQt::addChildAtIndex(GraphicsLayer* layer, int index)
564
{
565
    GraphicsLayer::addChildAtIndex(layer, index);
566
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
567
}
568
569
// reimp from GraphicsLayer.h
570
void GraphicsLayerQt::addChildAbove(GraphicsLayer* layer, GraphicsLayer* sibling)
571
{
572
     GraphicsLayer::addChildAbove(layer, sibling);
573
     m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
574
}
575
576
// reimp from GraphicsLayer.h
577
void GraphicsLayerQt::addChildBelow(GraphicsLayer* layer, GraphicsLayer* sibling)
578
{
579
580
    GraphicsLayer::addChildBelow(layer, sibling);
581
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
582
}
583
584
// reimp from GraphicsLayer.h
585
bool GraphicsLayerQt::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild)
586
{
587
    if (GraphicsLayer::replaceChild(oldChild, newChild)) {
588
        m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange);
589
        return true;
590
    }
591
592
    return false;
593
}
594
595
// reimp from GraphicsLayer.h
596
void GraphicsLayerQt::removeFromParent()
597
{
598
    if (parent())
599
        m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange);
600
    GraphicsLayer::removeFromParent();
601
}
602
603
// reimp from GraphicsLayer.h
604
void GraphicsLayerQt::setMaskLayer(GraphicsLayer* layer)
605
{
606
    GraphicsLayer::setMaskLayer(layer);
607
    m_impl->notifyChange(GraphicsLayerQtImpl::MaskLayerChange);
608
}
609
610
// reimp from GraphicsLayer.h
611
void GraphicsLayerQt::setPosition(const FloatPoint& p)
612
{
613
    if (position() != p)
614
       m_impl->notifyChange(GraphicsLayerQtImpl::PositionChange);
615
    GraphicsLayer::setPosition(p);
616
}
617
618
// reimp from GraphicsLayer.h
619
void GraphicsLayerQt::setAnchorPoint(const FloatPoint3D& p)
620
{
621
    if (anchorPoint() != p)
622
        m_impl->notifyChange(GraphicsLayerQtImpl::AnchorPointChange);
623
    GraphicsLayer::setAnchorPoint(p);
624
}
625
626
// reimp from GraphicsLayer.h
627
void GraphicsLayerQt::setSize(const FloatSize& size)
628
{
629
    if (this->size() != size)
630
        m_impl->notifyChange(GraphicsLayerQtImpl::SizeChange);
631
    GraphicsLayer::setSize(size);
632
}
633
634
// reimp from GraphicsLayer.h
635
void GraphicsLayerQt::setTransform(const TransformationMatrix& t)
636
{
637
    if (!m_impl->m_transformAnimationRunning && transform() != t)
638
       m_impl->notifyChange(GraphicsLayerQtImpl::TransformChange);
639
    GraphicsLayer::setTransform(t);
640
}
641
642
// reimp from GraphicsLayer.h
643
void GraphicsLayerQt::setChildrenTransform(const TransformationMatrix& t)
644
{
645
    GraphicsLayer::setChildrenTransform(t);
646
    m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenTransformChange);
647
}
648
649
// reimp from GraphicsLayer.h
650
void GraphicsLayerQt::setPreserves3D(bool b)
651
{
652
    if (b != preserves3D());
653
       m_impl->notifyChange(GraphicsLayerQtImpl::Preserves3DChange);
654
    GraphicsLayer::setPreserves3D(b);
655
}
656
657
// reimp from GraphicsLayer.h
658
void GraphicsLayerQt::setMasksToBounds(bool b)
659
{
660
    GraphicsLayer::setMasksToBounds(b);
661
    m_impl->notifyChange(GraphicsLayerQtImpl::MasksToBoundsChange);
662
}
663
664
// reimp from GraphicsLayer.h
665
void GraphicsLayerQt::setDrawsContent(bool b)
666
{
667
    m_impl->notifyChange(GraphicsLayerQtImpl::DrawsContentChange);
668
    GraphicsLayer::setDrawsContent(b);
669
}
670
671
// reimp from GraphicsLayer.h
672
void GraphicsLayerQt::setBackgroundColor(const Color& c)
673
{
674
    m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange);
675
    m_impl->m_pendingContent.backgroundColor = c;
676
    GraphicsLayer::setBackgroundColor(c);
677
}
678
679
// reimp from GraphicsLayer.h
680
void GraphicsLayerQt::clearBackgroundColor()
681
{
682
    m_impl->m_pendingContent.backgroundColor = QColor();
683
    m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange);
684
    GraphicsLayer::clearBackgroundColor();
685
}
686
687
// reimp from GraphicsLayer.h
688
void GraphicsLayerQt::setContentsOpaque(bool b)
689
{
690
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOpaqueChange);
691
    GraphicsLayer::setContentsOpaque(b);
692
}
693
694
// reimp from GraphicsLayer.h
695
void GraphicsLayerQt::setBackfaceVisibility(bool b)
696
{
697
    m_impl->notifyChange(GraphicsLayerQtImpl::BackfaceVisibilityChange);
698
    GraphicsLayer::setBackfaceVisibility(b);
699
}
700
701
// reimp from GraphicsLayer.h
702
void GraphicsLayerQt::setOpacity(float o)
703
{
704
    if (!m_impl->opacityAnimationRunning && opacity() != o)
705
       m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange);
706
    GraphicsLayer::setOpacity(o);
707
}
708
709
// reimp from GraphicsLayer.h
710
void GraphicsLayerQt::setContentsRect(const IntRect& r)
711
{
712
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsRectChange);
713
    GraphicsLayer::setContentsRect(r);
714
}
715
716
// reimp from GraphicsLayer.h
717
void GraphicsLayerQt::setContentsToImage(Image* image)
718
{
719
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange);
720
    m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType;
721
    GraphicsLayer::setContentsToImage(image);
722
    if (image) {
723
        QPixmap* pxm = image->nativeImageForCurrentFrame();
724
        if (pxm) {
725
            m_impl->m_pendingContent.pixmap = *pxm;
726
            m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::PixmapContentType;
727
            return;
728
        }        
729
    }
730
    m_impl->m_pendingContent.pixmap = QPixmap();
731
}
732
733
// reimp from GraphicsLayer.h
734
void GraphicsLayerQt::setContentsBackgroundColor(const Color& color)
735
{
736
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange);
737
    m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::ColorContentType;
738
    m_impl->m_pendingContent.contentsBackgroundColor = QColor(color);
739
    GraphicsLayer::setContentsBackgroundColor(color);
740
}
741
742
// reimp from GraphicsLayer.h
743
void GraphicsLayerQt::setGeometryOrientation(CompositingCoordinatesOrientation orientation)
744
{
745
    m_impl->notifyChange(GraphicsLayerQtImpl::GeometryOrientationChange);
746
    GraphicsLayer::setGeometryOrientation(orientation);
747
}
748
749
// reimp from GraphicsLayer.h
750
void GraphicsLayerQt::setContentsOrientation(CompositingCoordinatesOrientation orientation)
751
{
752
    m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOrientationChange);
753
    GraphicsLayer::setContentsOrientation(orientation);
754
}
755
756
// reimp from GraphicsLayer.h
757
void GraphicsLayerQt::distributeOpacity(float o)
758
{
759
    m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange);
760
    m_impl->m_state.distributeOpacity = true;
761
}
762
763
// reimp from GraphicsLayer.h
764
float GraphicsLayerQt::accumulatedOpacity() const
765
{
766
    return m_impl->effectiveOpacity();
767
}
768
769
// reimp from GraphicsLayer.h
770
void GraphicsLayerQt::syncCompositingState()
771
{
772
    m_impl->flushChanges();
773
    GraphicsLayer::syncCompositingState();
774
}
775
776
// reimp from GraphicsLayer.h
777
NativeLayer GraphicsLayerQt::nativeLayer() const
778
{
779
    return m_impl.get();
780
}
781
782
// reimp from GraphicsLayer.h
783
PlatformLayer* GraphicsLayerQt::platformLayer() const
784
{
785
    return m_impl.get();
786
}
787
788
// now we start dealing with WebCore animations translated to Qt animations
789
790
template <typename T>
791
struct KeyframeValueQt {
792
    TimingFunction timingFunction;
793
    T value;
794
};
795
796
// we copy this from the AnimationBase.cpp
797
static inline double solveEpsilon(double duration)
798
{
799
    return 1.0 / (200.0 * duration);
800
}
801
802
static inline double solveCubicBezierFunction(qreal p1x, qreal p1y, qreal p2x, qreal p2y, double t, double duration)
803
{
804
    UnitBezier bezier(p1x, p1y, p2x, p2y);
805
    return bezier.solve(t, solveEpsilon(duration));
806
}
807
808
// we want the timing function to be as close as possible to what the web-developer intended, so we're using the same function used by WebCore when compositing is disabled
809
// Using easing-curves would probably work for some of the cases, but wouldn't really buy us anything as we'd have to convert the bezier function back to an easing curve
810
static inline qreal applyTimingFunction(const TimingFunction& timingFunction, qreal progress, int duration)
811
{
812
        if (timingFunction.type() == LinearTimingFunction)
813
            return progress;
814
        if (timingFunction.type() == CubicBezierTimingFunction) {
815
            return solveCubicBezierFunction(timingFunction.x1(),
816
                                            timingFunction.y1(),
817
                                            timingFunction.x2(),
818
                                            timingFunction.y2(),
819
                                            double(progress), double(duration) / 1000);
820
        }
821
        return progress;
822
}
823
824
// helper functions to safely get a value out of WebCore's AnimationValue*
825
static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, TransformOperations& transformOperations)
826
{
827
    transformOperations = TransformOperations();
828
    if (!animationValue)
829
        return;
830
831
    const TransformOperations* ops = static_cast<const TransformAnimationValue*>(animationValue)->value();
832
833
    if (ops)
834
        transformOperations = *ops;
835
}
836
837
static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, qreal& realValue)
838
{
839
    realValue = animationValue ? static_cast<const FloatAnimationValue*>(animationValue)->value() : 0;
840
}
841
842
// we put a bit of the functionality in a base class to allow casting and to save some code size
843
class AnimationQtBase : public QAbstractAnimation {
844
public:
845
    AnimationQtBase(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
846
            : QAbstractAnimation(0)
847
            , m_layer(layer)
848
            , m_boxSize(boxSize)
849
            , m_duration(anim->duration() * 1000)
850
            , m_isAlternate(anim->direction() == Animation::AnimationDirectionAlternate)
851
            , m_webkitPropertyID(values.property())
852
            , m_keyframesName(name)
853
    {
854
    }
855
856
    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
857
    {
858
        QAbstractAnimation::updateState(newState, oldState);
859
860
        // for some reason I have do this asynchronously - or the animation won't work
861
        if (newState == Running && oldState == Stopped)
862
            QTimer::singleShot(0, m_layer.data(), SLOT(notifyAnimationStarted()));
863
    }
864
865
    virtual int duration() const { return m_duration; }
866
867
    QWeakPointer<GraphicsLayerQtImpl> m_layer;
868
    IntSize m_boxSize;
869
    int m_duration;
870
    bool m_isAlternate;
871
    AnimatedPropertyID m_webkitPropertyID;
872
    QString m_keyframesName;
873
};
874
875
// we'd rather have a templatized QAbstractAnimation than QPropertyAnimation / QVariantAnimation;
876
// Since we know the types that we're dealing with, the QObject/QProperty/QVariant abstraction
877
// buys us very little in this case, for too much overhead
878
template <typename T>
879
class AnimationQt : public AnimationQtBase {
880
881
public:
882
    AnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
883
            :AnimationQtBase(layer, values, boxSize, anim, name)
884
    {
885
        // copying those WebCore structures is not trivial, we have to do it like this
886
        for (size_t i = 0; i < values.size(); ++i) {
887
            const AnimationValue* animationValue = values.at(i);
888
            KeyframeValueQt<T> keyframeValue;
889
            if (animationValue->timingFunction())
890
                keyframeValue.timingFunction = *animationValue->timingFunction();
891
            webkitAnimationToQtAnimationValue(animationValue, keyframeValue.value);
892
            keyframeValues[animationValue->keyTime()] = keyframeValue;
893
        }
894
    }
895
896
protected:
897
898
    // this is the part that differs between animated properties
899
    virtual void applyFrame(const T& fromValue, const T& toValue, qreal progress) = 0;
900
901
    virtual void updateCurrentTime(int currentTime)
902
    {
903
        if (!m_layer)
904
            return;
905
906
        qreal progress = qreal(currentLoopTime()) / duration();
907
908
        if (m_isAlternate && currentLoop()%2)
909
            progress = 1-progress;
910
911
        if (keyframeValues.isEmpty())
912
            return;
913
914
        // we find the current from-to keyframes in our little map
915
        typename QMap<qreal, KeyframeValueQt<T> >::iterator it = keyframeValues.find(progress);
916
917
        // we didn't find an exact match, we try the closest match (lower bound)
918
        if (it == keyframeValues.end())
919
            it = keyframeValues.lowerBound(progress)-1;
920
921
        // we didn't find any match - we use the first keyframe
922
        if (it == keyframeValues.end())
923
            it = keyframeValues.begin();
924
925
        typename QMap<qreal, KeyframeValueQt<T> >::iterator it2 = it+1;
926
        if (it2 == keyframeValues.end())
927
            it2 = keyframeValues.begin();
928
        const KeyframeValueQt<T>& fromKeyframe = it.value();
929
        const KeyframeValueQt<T>& toKeyframe = it2.value();
930
931
        const TimingFunction& timingFunc = fromKeyframe.timingFunction;
932
        const T& fromValue = fromKeyframe.value;
933
        const T& toValue = toKeyframe.value;
934
935
        // now we have a source keyframe, origin keyframe and a timing function
936
        // we can now process the progress and apply the frame
937
        qreal normalizedProgress = (it.key() == it2.key()) ? 0 : (progress - it.key()) / (it2.key() - it.key());
938
        normalizedProgress = applyTimingFunction(timingFunc, normalizedProgress, duration() / 1000);
939
        applyFrame(fromValue, toValue, normalizedProgress);
940
    }
941
942
    QMap<qreal, KeyframeValueQt<T> > keyframeValues;
943
};
944
945
class TransformAnimationQt : public AnimationQt<TransformOperations> {
946
public:
947
    TransformAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
948
                : AnimationQt<TransformOperations>(layer, values, boxSize, anim, name)
949
    {
950
    }
951
952
    ~TransformAnimationQt()
953
    {
954
        // this came up during the compositing/animation LayoutTests
955
        // when the animation dies, the transform has to go back to default
956
        m_layer.data()->setBaseTransform(QTransform(m_layer.data()->m_layer->transform()));
957
    }
958
959
    // the idea is that we let WebCore manage the transform-operations
960
    // and Qt just manages the animation heartbeat and the bottom-line QTransform
961
    // we get the performance not by using QTransform instead of TransformationMatrix, but by proper caching of
962
    // items that are expensive for WebCore to render. We want the rest to be as close to WebCore's idea as possible.
963
    virtual void applyFrame(const TransformOperations& sourceOperations, const TransformOperations& targetOperations, qreal progress)
964
    {
965
        TransformationMatrix transformMatrix;
966
967
        // this looks simple but is really tricky to get right. Use caution.
968
        for (size_t i = 0; i < targetOperations.size(); ++i)
969
            targetOperations.operations()[i]->blend(sourceOperations.at(i), progress)->apply(transformMatrix, m_boxSize);
970
971
        m_layer.data()->setBaseTransform(QTransform(transformMatrix));
972
    }
973
974
    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
975
    {
976
        AnimationQtBase::updateState(newState, oldState);
977
        if (!m_layer)
978
            return;
979
        m_layer.data()->flushChanges(true);
980
981
        // to increase FPS, we use a less accurate caching mechanism while animation is going on
982
        // this is a UX choice that should probably be customizable
983
        if (newState == QAbstractAnimation::Running) {
984
            m_layer.data()->m_transformAnimationRunning = true;
985
            if (m_layer.data()->cacheMode() == QGraphicsItem::DeviceCoordinateCache)
986
                m_layer.data()->setCacheMode(QGraphicsItem::ItemCoordinateCache);
987
        } else {
988
            m_layer.data()->m_transformAnimationRunning = false;
989
            if (m_layer.data()->cacheMode() == QGraphicsItem::ItemCoordinateCache)
990
                m_layer.data()->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
991
        }
992
    }
993
};
994
995
class OpacityAnimationQt : public AnimationQt<qreal> {
996
public:
997
    OpacityAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name)
998
                : AnimationQt<qreal>(layer, values, boxSize, anim, name)
999
    {
1000
    }
1001
1002
    virtual void applyFrame(const qreal& fromValue, const qreal& toValue, qreal progress)
1003
    {
1004
        m_layer.data()->setOpacity(qMin<qreal>(qMax<qreal>(fromValue + (toValue-fromValue)*progress, 0), 1));
1005
    }
1006
1007
    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
1008
    {
1009
        QAbstractAnimation::updateState(newState, oldState);
1010
1011
        if (!m_layer)
1012
            return;
1013
1014
        if (newState == QAbstractAnimation::Running)
1015
            m_layer.data()->opacityAnimationRunning = true;
1016
        else
1017
            m_layer.data()->opacityAnimationRunning = false;
1018
    }
1019
};
1020
1021
bool GraphicsLayerQt::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset)
1022
{
1023
    if (!anim->duration() || !anim->iterationCount())
1024
        return false;
1025
1026
    QAbstractAnimation* newAnim;
1027
1028
    switch (values.property()) {
1029
    case AnimatedPropertyOpacity:
1030
        newAnim = new OpacityAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName);
1031
        break;
1032
    case AnimatedPropertyWebkitTransform:
1033
        newAnim = new TransformAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName);
1034
        break;
1035
    default:
1036
        return false;
1037
    }
1038
1039
    // we make sure WebCore::Animation and QAnimation are on the same terms
1040
    newAnim->setLoopCount(anim->iterationCount());
1041
    m_impl->m_animations.append(QWeakPointer<QAbstractAnimation>(newAnim));
1042
    QObject::connect(&m_impl->m_suspendTimer, SIGNAL(timeout()), newAnim, SLOT(resume()));
1043
    timeOffset += anim->delay();
1044
1045
    // flush now or flicker...
1046
    m_impl->flushChanges(false);
1047
1048
    if (timeOffset)
1049
        QTimer::singleShot(timeOffset * 1000, newAnim, SLOT(start()));
1050
    else
1051
        newAnim->start();
1052
1053
    QObject::connect(newAnim, SIGNAL(finished()), newAnim, SLOT(deleteLater()));
1054
1055
    return true;
1056
}
1057
1058
void GraphicsLayerQt::removeAnimationsForProperty(AnimatedPropertyID id)
1059
{
1060
    for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
1061
        if (*it) {
1062
            AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data());
1063
            if (anim && anim->m_webkitPropertyID == id) {
1064
                delete anim;
1065
                it = m_impl->m_animations.erase(it);
1066
                --it;
1067
            }
1068
        }
1069
    }
1070
}
1071
1072
void GraphicsLayerQt::removeAnimationsForKeyframes(const String& name)
1073
{
1074
    for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
1075
        if (*it) {
1076
            AnimationQtBase* anim = static_cast<AnimationQtBase*>((*it).data());
1077
            if (anim && anim->m_keyframesName == QString(name)) {
1078
                (*it).data()->deleteLater();
1079
                it = m_impl->m_animations.erase(it);
1080
                --it;
1081
            }
1082
        }
1083
    }
1084
}
1085
1086
void GraphicsLayerQt::pauseAnimation(const String& name, double timeOffset)
1087
{
1088
    for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
1089
        if (*it) {
1090
            AnimationQtBase* anim = static_cast<AnimationQtBase*>((*it).data());
1091
            if (anim && anim->m_keyframesName == QString(name))
1092
                QTimer::singleShot(timeOffset * 1000, anim, SLOT(pause()));
1093
        }
1094
    }
1095
}
1096
1097
void GraphicsLayerQt::suspendAnimations(double time)
1098
{
1099
    if (m_impl->m_suspendTimer.isActive()) {
1100
        m_impl->m_suspendTimer.stop();
1101
        m_impl->m_suspendTimer.start(time * 1000);
1102
    } else {
1103
        for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
1104
            QAbstractAnimation* anim = it->data();
1105
            if (anim)
1106
                anim->pause();
1107
        }
1108
    }
1109
}
1110
1111
void GraphicsLayerQt::resumeAnimations()
1112
{
1113
    if (m_impl->m_suspendTimer.isActive()) {
1114
        m_impl->m_suspendTimer.stop();
1115
        for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) {
1116
            QAbstractAnimation* anim = (*it).data();
1117
            if (anim)
1118
                anim->resume();
1119
        }
1120
    }
1121
}
1122
1123
}
1124
1125
#include <GraphicsLayerQt.moc>