/* | |
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) | |
This library is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Library General Public | |
License as published by the Free Software Foundation; either | |
version 2 of the License, or (at your option) any later version. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Library General Public License for more details. | |
You should have received a copy of the GNU Library General Public License | |
along with this library; see the file COPYING.LIB. If not, write to | |
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
Boston, MA 02110-1301, USA. | |
*/ | |
#include "config.h" | |
#include "GraphicsLayerQt.h" | |
#include "CurrentTime.h" | |
#include "FloatRect.h" | |
#include "GraphicsContext.h" | |
#include "Image.h" | |
#include "RefCounted.h" | |
#include "TranslateTransformOperation.h" | |
#include "UnitBezier.h" | |
#include <QtCore/qabstractanimation.h> | |
#include <QtCore/qdebug.h> | |
#include <QtCore/qmetaobject.h> | |
#include <QtCore/qset.h> | |
#include <QtCore/qtimer.h> | |
#include <QtGui/qcolor.h> | |
#include <QtGui/qgraphicseffect.h> | |
#include <QtGui/qgraphicsitem.h> | |
#include <QtGui/qgraphicsscene.h> | |
#include <QtGui/qpainter.h> | |
#include <QtGui/qpixmap.h> | |
#include <QtGui/qpixmapcache.h> | |
#include <QtGui/qstyleoption.h> | |
#define QT_DEBUG_RECACHE 0 | |
#define QT_DEBUG_CACHEDUMP 0 | |
namespace WebCore { | |
#ifndef QT_NO_GRAPHICSEFFECT | |
class MaskEffectQt : public QGraphicsEffect { | |
public: | |
MaskEffectQt(QObject* parent, QGraphicsItem* maskLayer) | |
: QGraphicsEffect(parent) | |
, m_maskLayer(maskLayer) | |
{ | |
} | |
void draw(QPainter* painter) | |
{ | |
// this is a modified clone of QGraphicsOpacityEffect. | |
// It's more efficient to do it this way because | |
// (a) we don't need the QBrush abstraction - we always end up using QGraphicsItem::paint from the mask layer | |
// (b) QGraphicsOpacityEffect detaches the pixmap, which is inefficient on OpenGL. | |
const QSize maskSize = sourceBoundingRect().toAlignedRect().size(); | |
if (!maskSize.isValid() || maskSize.isEmpty()) { | |
drawSource(painter); | |
return; | |
} | |
QPixmap maskPixmap(maskSize); | |
// we need to do this so the pixmap would have hasAlpha() | |
maskPixmap.fill(Qt::transparent); | |
QPainter maskPainter(&maskPixmap); | |
QStyleOptionGraphicsItem option; | |
option.exposedRect = option.rect = maskPixmap.rect(); | |
maskPainter.setRenderHints(painter->renderHints(), true); | |
m_maskLayer->paint(&maskPainter, &option, 0); | |
maskPainter.end(); | |
QPoint offset; | |
QPixmap srcPixmap = sourcePixmap(Qt::LogicalCoordinates, &offset, QGraphicsEffect::NoPad); | |
// we have to use another intermediate pixmap, to make sure the mask applies only to this item | |
// and doesn't modify pixels already painted into this paint-device | |
QPixmap pixmap(srcPixmap.size()); | |
pixmap.fill(Qt::transparent); | |
if (pixmap.isNull()) | |
return; | |
QPainter pixmapPainter(&pixmap); | |
pixmapPainter.setRenderHints(painter->renderHints()); | |
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source); | |
// We use drawPixmap rather than detaching, because it's more efficient on OpenGL | |
pixmapPainter.drawPixmap(0, 0, srcPixmap); | |
pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); | |
pixmapPainter.drawPixmap(0, 0, maskPixmap); | |
pixmapPainter.end(); | |
painter->drawPixmap(offset, pixmap); | |
} | |
QGraphicsItem* m_maskLayer; | |
}; | |
#endif // QT_NO_GRAPHICSEFFECT | |
class GraphicsLayerQtImpl : public QGraphicsObject { | |
Q_OBJECT | |
public: | |
// this set of flags help us defer which properties of the layer have been | |
// modified by the compositor, so we can know what to look for in the next flush | |
enum ChangeMask { | |
NoChanges = 0, | |
ParentChange = (1L << 0), | |
ChildrenChange = (1L << 1), | |
MaskLayerChange = (1L << 2), | |
PositionChange = (1L << 3), | |
AnchorPointChange = (1L << 4), | |
SizeChange = (1L << 5), | |
TransformChange = (1L << 6), | |
ContentChange = (1L << 7), | |
GeometryOrientationChange = (1L << 8), | |
ContentsOrientationChange = (1L << 9), | |
OpacityChange = (1L << 10), | |
ContentsRectChange = (1L << 11), | |
Preserves3DChange = (1L << 12), | |
MasksToBoundsChange = (1L << 13), | |
DrawsContentChange = (1L << 14), | |
ContentsOpaqueChange = (1L << 15), | |
BackfaceVisibilityChange = (1L << 16), | |
ChildrenTransformChange = (1L << 17), | |
DisplayChange = (1L << 18), | |
BackgroundColorChange = (1L << 19), | |
DistributesOpacityChange = (1L << 20) | |
}; | |
// the compositor lets us special-case images and colors, so we try to do so | |
enum StaticContentType { HTMLContentType, PixmapContentType, ColorContentType, MediaContentType}; | |
const GraphicsLayerQtImpl* rootLayer() const; | |
GraphicsLayerQtImpl(GraphicsLayerQt* newLayer); | |
virtual ~GraphicsLayerQtImpl(); | |
// reimps from QGraphicsItem | |
virtual QPainterPath opaqueArea() const; | |
virtual QRectF boundingRect() const; | |
virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); | |
// We manage transforms ourselves because transform-origin acts differently in webkit and in Qt, and we need it as a fallback in case we encounter an un-invertible matrix | |
void setBaseTransform(const TransformationMatrix&); | |
void updateTransform(); | |
// let the compositor-API tell us which properties were changed | |
void notifyChange(ChangeMask); | |
// actual rendering of the web-content into a QPixmap | |
// We prefer to use our own caching because it gives us a higher level of granularity than QGraphicsItem cache modes - | |
// sometimes we need to cache the contents even `though the item needs to be updated, e.g. when the background-color is changed. | |
// TODO: investigate if QGraphicsItem caching can be improved to support that out of the box. | |
QPixmap recache(const QRegion&); | |
// called when the compositor is ready for us to show the changes on screen | |
// this is called indirectly from ChromeClientQt::setNeedsOneShotDrawingSynchronization | |
// (meaning the sync would happen together with the next draw) | |
// or ChromeClientQt::scheduleCompositingLayerSync (meaning the sync will happen ASAP) | |
void flushChanges(bool recursive = true, bool forceTransformUpdate = false); | |
public slots: | |
// we need to notify the client (aka the layer compositor) when the animation actually starts | |
void notifyAnimationStarted(); | |
// we notify WebCore of a layer changed asynchronously; otherwise we end up calling flushChanges too often. | |
void notifySyncRequired(); | |
signals: | |
// optimization: we don't want to use QTimer::singleShot | |
void notifyAnimationStartedAsync(); | |
public: | |
GraphicsLayerQt* m_layer; | |
TransformationMatrix m_baseTransform; | |
TransformationMatrix m_transformRelativeToRootLayer; | |
bool m_transformAnimationRunning; | |
bool m_opacityAnimationRunning; | |
#ifndef QT_NO_GRAPHICSEFFECT | |
QWeakPointer<MaskEffectQt> m_maskEffect; | |
#endif | |
struct ContentData { | |
QPixmap pixmap; | |
QRegion regionToUpdate; | |
bool updateAll; | |
QColor contentsBackgroundColor; | |
QColor backgroundColor; | |
QWeakPointer<QGraphicsObject> mediaLayer; | |
StaticContentType contentType; | |
float opacity; | |
ContentData() | |
: updateAll(false) | |
, contentType(HTMLContentType) | |
, opacity(1.f) | |
{ | |
} | |
}; | |
ContentData m_pendingContent; | |
ContentData m_currentContent; | |
int m_changeMask; | |
QSizeF m_size; | |
struct { | |
QPixmapCache::Key key; | |
QSizeF size; | |
} m_backingStore; | |
#ifndef QT_NO_ANIMATION | |
QList<QWeakPointer<QAbstractAnimation> > m_animations; | |
#endif | |
QTimer m_suspendTimer; | |
struct State { | |
GraphicsLayer* maskLayer; | |
FloatPoint pos; | |
FloatPoint3D anchorPoint; | |
FloatSize size; | |
TransformationMatrix transform; | |
TransformationMatrix childrenTransform; | |
Color backgroundColor; | |
Color currentColor; | |
GraphicsLayer::CompositingCoordinatesOrientation geoOrientation; | |
GraphicsLayer::CompositingCoordinatesOrientation contentsOrientation; | |
float opacity; | |
QRect contentsRect; | |
bool preserves3D: 1; | |
bool masksToBounds: 1; | |
bool drawsContent: 1; | |
bool contentsOpaque: 1; | |
bool backfaceVisibility: 1; | |
bool distributeOpacity: 1; | |
bool align: 2; | |
State(): maskLayer(0), opacity(1.f), preserves3D(false), masksToBounds(false), | |
drawsContent(false), contentsOpaque(false), backfaceVisibility(false), | |
distributeOpacity(false) | |
{ | |
} | |
} m_state; | |
#ifndef QT_NO_ANIMATION | |
friend class AnimationQtBase; | |
#endif | |
}; | |
GraphicsLayerQtImpl::GraphicsLayerQtImpl(GraphicsLayerQt* newLayer) | |
: QGraphicsObject(0) | |
, m_layer(newLayer) | |
, m_transformAnimationRunning(false) | |
, m_opacityAnimationRunning(false) | |
, m_changeMask(NoChanges) | |
{ | |
// we use graphics-view for compositing, not for interactivity | |
setAcceptedMouseButtons(Qt::NoButton); | |
// we need to have the item enabled, or else wheel events are not | |
// passed to the parent class implementation of wheelEvent, where | |
// they are ignored and passed to the item below. | |
setEnabled(true); | |
connect(this, SIGNAL(notifyAnimationStartedAsync()), this, SLOT(notifyAnimationStarted()), Qt::QueuedConnection); | |
} | |
GraphicsLayerQtImpl::~GraphicsLayerQtImpl() | |
{ | |
// the compositor manages item lifecycle - we don't want the graphics-view | |
// system to automatically delete our items | |
const QList<QGraphicsItem*> children = childItems(); | |
for (QList<QGraphicsItem*>::const_iterator it = children.begin(); it != children.end(); ++it) { | |
if (QGraphicsItem* item = *it) { | |
if (scene()) | |
scene()->removeItem(item); | |
item->setParentItem(0); | |
} | |
} | |
#ifndef QT_NO_ANIMATION | |
// we do, however, own the animations... | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_animations.begin(); it != m_animations.end(); ++it) | |
if (QAbstractAnimation* anim = it->data()) | |
delete anim; | |
#endif | |
} | |
const GraphicsLayerQtImpl* GraphicsLayerQtImpl::rootLayer() const | |
{ | |
if (const GraphicsLayerQtImpl* parent = qobject_cast<const GraphicsLayerQtImpl*>(parentObject())) | |
return parent->rootLayer(); | |
return this; | |
} | |
QPixmap GraphicsLayerQtImpl::recache(const QRegion& regionToUpdate) | |
{ | |
if (!m_layer->drawsContent() || m_size.isEmpty() || !m_size.isValid()) | |
return QPixmap(); | |
QPixmap pixmap; | |
QRegion region = regionToUpdate; | |
if (QPixmapCache::find(m_backingStore.key, &pixmap)) { | |
if (region.isEmpty()) | |
return pixmap; | |
QPixmapCache::remove(m_backingStore.key); // Remove the reference to the pixmap in the cache to avoid a detach. | |
} | |
{ | |
bool erased = false; | |
// If the pixmap is not in the cache or the view has grown since last cached. | |
if (pixmap.isNull() || m_size != m_backingStore.size) { | |
#if QT_DEBUG_RECACHE | |
if (pixmap.isNull()) | |
qDebug() << "CacheMiss" << this << m_size; | |
#endif | |
bool fill = true; | |
QRegion newRegion; | |
QPixmap oldPixmap = pixmap; | |
// If the pixmap is two small to hold the view contents we enlarge, otherwise just use the old (large) pixmap. | |
if (pixmap.width() < m_size.width() || pixmap.height() < m_size.height()) { | |
#if QT_DEBUG_RECACHE | |
qDebug() << "CacheGrow" << this << m_size; | |
#endif | |
pixmap = QPixmap(m_size.toSize()); | |
pixmap.fill(Qt::transparent); | |
newRegion = QRegion(0, 0, m_size.width(), m_size.height()); | |
} | |
#if 1 | |
// Blit the contents of oldPixmap back into the cached pixmap as we are just adding new pixels. | |
if (!oldPixmap.isNull()) { | |
const QRegion cleanRegion = (QRegion(0, 0, m_size.width(), m_size.height()) | |
& QRegion(0, 0, m_backingStore.size.width(), m_backingStore.size.height())) - regionToUpdate; | |
if (!cleanRegion.isEmpty()) { | |
#if QT_DEBUG_RECACHE | |
qDebug() << "CacheBlit" << this << cleanRegion; | |
#endif | |
const QRect cleanBounds(cleanRegion.boundingRect()); | |
QPainter painter(&pixmap); | |
painter.setCompositionMode(QPainter::CompositionMode_Source); | |
painter.drawPixmap(cleanBounds.topLeft(), oldPixmap, cleanBounds); | |
newRegion -= cleanRegion; | |
fill = false; // We cannot just fill the pixmap. | |
} | |
oldPixmap = QPixmap(); | |
} | |
#endif | |
region += newRegion; | |
if (fill && !region.isEmpty()) { // Clear the entire pixmap with the background. | |
#if QT_DEBUG_RECACHE | |
qDebug() << "CacheErase" << this << m_size << background; | |
#endif | |
erased = true; | |
pixmap.fill(Qt::transparent); | |
} | |
} | |
region &= QRegion(0, 0, m_size.width(), m_size.height()); | |
// If we have something to draw its time to erase it and render the contents. | |
if (!region.isEmpty()) { | |
#if QT_DEBUG_CACHEDUMP | |
static int recacheCount = 0; | |
++recacheCount; | |
qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; | |
pixmap.save(QString().sprintf("/tmp/%05d_A.png", recacheCount), "PNG"); | |
#endif | |
QPainter painter(&pixmap); | |
GraphicsContext gc(&painter); | |
painter.setClipRegion(region); | |
if (!erased) { // Erase the area in cache that we're drawing into. | |
painter.setCompositionMode(QPainter::CompositionMode_Clear); | |
painter.fillRect(region.boundingRect(), Qt::transparent); | |
#if QT_DEBUG_CACHEDUMP | |
qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; | |
pixmap.save(QString().sprintf("/tmp/%05d_B.png", recacheCount), "PNG"); | |
#endif | |
} | |
// Render the actual contents into the cache. | |
painter.setCompositionMode(QPainter::CompositionMode_SourceOver); | |
m_layer->paintGraphicsLayerContents(gc, region.boundingRect()); | |
painter.end(); | |
#if QT_DEBUG_CACHEDUMP | |
qDebug() << "**** CACHEDUMP" << recacheCount << this << m_layer << region << m_size; | |
pixmap.save(QString().sprintf("/tmp/%05d_C.png", recacheCount), "PNG"); | |
#endif | |
} | |
m_backingStore.size = m_size; // Store the used size of the pixmap. | |
} | |
// Finally insert into the cache and allow a reference there. | |
m_backingStore.key = QPixmapCache::insert(pixmap); | |
return pixmap; | |
} | |
void GraphicsLayerQtImpl::updateTransform() | |
{ | |
if (!m_transformAnimationRunning) | |
m_baseTransform = m_layer->transform(); | |
TransformationMatrix localTransform; | |
GraphicsLayerQtImpl* parent = qobject_cast<GraphicsLayerQtImpl*>(parentObject()); | |
// webkit has relative-to-size originPoint, graphics-view has a pixel originPoint, here we convert | |
// we have to manage this ourselves because QGraphicsView's transformOrigin is incompatible | |
const qreal originX = m_state.anchorPoint.x() * m_size.width(); | |
const qreal originY = m_state.anchorPoint.y() * m_size.height(); | |
// We ignore QGraphicsItem::pos completely, and use only transforms - because we have to maintain that ourselves for 3D. | |
localTransform | |
.translate3d(originX + m_state.pos.x(), originY + m_state.pos.y(), m_state.anchorPoint.z()) | |
.multLeft(m_baseTransform) | |
.translate3d(-originX, -originY, -m_state.anchorPoint.z()); | |
// This is the actual 3D transform of this item, with the ancestors' transform baked in. | |
m_transformRelativeToRootLayer = TransformationMatrix(parent ? parent->m_transformRelativeToRootLayer : TransformationMatrix()) | |
.multLeft(localTransform); | |
// Now we have enough information to determine if the layer is facing backwards. | |
if (!m_state.backfaceVisibility && m_transformRelativeToRootLayer.inverse().m33() < 0) { | |
setVisible(false); | |
// No point in making extra calculations for invisible elements. | |
return; | |
} | |
// The item is front-facing or backface-visibility is on. | |
setVisible(true); | |
// Flatten to 2D-space of this item if it doesn't preserve 3D. | |
if (!m_state.preserves3D) { | |
m_transformRelativeToRootLayer.setM13(0); | |
m_transformRelativeToRootLayer.setM23(0); | |
m_transformRelativeToRootLayer.setM31(0); | |
m_transformRelativeToRootLayer.setM32(0); | |
m_transformRelativeToRootLayer.setM33(1); | |
m_transformRelativeToRootLayer.setM34(0); | |
m_transformRelativeToRootLayer.setM43(0); | |
} | |
// Apply perspective for the use of this item's children. Perspective is always applied from the item's center. | |
if (!m_state.childrenTransform.isIdentity()) | |
m_transformRelativeToRootLayer | |
.translate(m_size.width() / 2, m_size.height() /2) | |
.multLeft(m_state.childrenTransform) | |
.translate(-m_size.width() / 2, -m_size.height() /2); | |
bool inverseOk = true; | |
// Use QTransform::inverse to extrapolate the relative transform of this item, based on the parent's transform relative to | |
// the root layer and the desired transform for this item relative to the root layer. | |
const QTransform parentTransform = parent ? parent->itemTransform(rootLayer()) : QTransform(); | |
const QTransform transform2D = QTransform(m_transformRelativeToRootLayer) * parentTransform.inverted(&inverseOk); | |
// In rare cases the transformation cannot be inversed - in that case we don't apply the transformation at all, otherwise we'd flicker. | |
// FIXME: This should be amended when Qt moves to a real 3D scene-graph. | |
if (!inverseOk) | |
return; | |
setTransform(transform2D); | |
const QList<QGraphicsItem*> children = childItems(); | |
for (QList<QGraphicsItem*>::const_iterator it = children.begin(); it != children.end(); ++it) | |
if (GraphicsLayerQtImpl* layer= qobject_cast<GraphicsLayerQtImpl*>((*it)->toGraphicsObject())) | |
layer->updateTransform(); | |
} | |
void GraphicsLayerQtImpl::setBaseTransform(const TransformationMatrix& baseTransform) | |
{ | |
m_baseTransform = baseTransform; | |
updateTransform(); | |
} | |
QPainterPath GraphicsLayerQtImpl::opaqueArea() const | |
{ | |
QPainterPath painterPath; | |
// we try out best to return the opaque area, maybe it will help graphics-view render less items | |
if (m_currentContent.backgroundColor.isValid() && m_currentContent.backgroundColor.alpha() == 0xff) | |
painterPath.addRect(boundingRect()); | |
else { | |
if (m_state.contentsOpaque | |
|| (m_currentContent.contentType == ColorContentType && m_currentContent.contentsBackgroundColor.alpha() == 0xff) | |
|| (m_currentContent.contentType == MediaContentType) | |
|| (m_currentContent.contentType == PixmapContentType && !m_currentContent.pixmap.hasAlpha())) { | |
painterPath.addRect(m_state.contentsRect); | |
} | |
} | |
return painterPath; | |
} | |
QRectF GraphicsLayerQtImpl::boundingRect() const | |
{ | |
return QRectF(QPointF(0, 0), QSizeF(m_size)); | |
} | |
void GraphicsLayerQtImpl::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) | |
{ | |
if (m_currentContent.backgroundColor.isValid()) | |
painter->fillRect(option->rect, QColor(m_currentContent.backgroundColor)); | |
switch (m_currentContent.contentType) { | |
case HTMLContentType: | |
if (m_state.drawsContent) { | |
QPixmap backingStore; | |
// We might need to recache, in case we try to paint and the cache was purged (e.g. if it was full). | |
if (!QPixmapCache::find(m_backingStore.key, &backingStore) || backingStore.size() != m_size.toSize()) | |
backingStore = recache(QRegion(m_state.contentsRect)); | |
const QRectF bounds(0, 0, m_backingStore.size.width(), m_backingStore.size.height()); | |
painter->drawPixmap(0, 0, backingStore); | |
} | |
break; | |
case PixmapContentType: | |
painter->drawPixmap(m_state.contentsRect, m_currentContent.pixmap); | |
break; | |
case ColorContentType: | |
painter->fillRect(m_state.contentsRect, m_currentContent.contentsBackgroundColor); | |
break; | |
case MediaContentType: | |
// we don't need to paint anything: we have a QGraphicsItem from the media element | |
break; | |
} | |
} | |
void GraphicsLayerQtImpl::notifySyncRequired() | |
{ | |
if (m_layer->client()) | |
m_layer->client()->notifySyncRequired(m_layer); | |
} | |
void GraphicsLayerQtImpl::notifyChange(ChangeMask changeMask) | |
{ | |
m_changeMask |= changeMask; | |
static QMetaMethod syncMethod = staticMetaObject.method(staticMetaObject.indexOfMethod("notifySyncRequired()")); | |
syncMethod.invoke(this, Qt::QueuedConnection); | |
} | |
void GraphicsLayerQtImpl::flushChanges(bool recursive, bool forceUpdateTransform) | |
{ | |
// this is the bulk of the work. understanding what the compositor is trying to achieve, | |
// what graphics-view can do, and trying to find a sane common-grounds | |
if (!m_layer || m_changeMask == NoChanges) | |
goto afterLayerChanges; | |
if (m_currentContent.contentType == HTMLContentType && (m_changeMask & ParentChange)) { | |
// the WebCore compositor manages item ownership. We have to make sure | |
// graphics-view doesn't try to snatch that ownership... | |
if (!m_layer->parent() && !parentItem()) | |
setParentItem(0); | |
else if (m_layer && m_layer->parent() && m_layer->parent()->nativeLayer() != parentItem()) | |
setParentItem(m_layer->parent()->nativeLayer()); | |
} | |
if (m_changeMask & ChildrenChange) { | |
// we basically do an XOR operation on the list of current children | |
// and the list of wanted children, and remove/add | |
QSet<QGraphicsItem*> newChildren; | |
const Vector<GraphicsLayer*> newChildrenVector = (m_layer->children()); | |
newChildren.reserve(newChildrenVector.size()); | |
for (size_t i = 0; i < newChildrenVector.size(); ++i) | |
newChildren.insert(newChildrenVector[i]->platformLayer()); | |
const QSet<QGraphicsItem*> currentChildren = childItems().toSet(); | |
const QSet<QGraphicsItem*> childrenToAdd = newChildren - currentChildren; | |
const QSet<QGraphicsItem*> childrenToRemove = currentChildren - newChildren; | |
for (QSet<QGraphicsItem*>::const_iterator it = childrenToAdd.begin(); it != childrenToAdd.end(); ++it) | |
if (QGraphicsItem* w = *it) | |
w->setParentItem(this); | |
for (QSet<QGraphicsItem*>::const_iterator it = childrenToRemove.begin(); it != childrenToRemove.end(); ++it) | |
if (GraphicsLayerQtImpl* w = qobject_cast<GraphicsLayerQtImpl*>((*it)->toGraphicsObject())) | |
w->setParentItem(0); | |
// children are ordered by z-value, let graphics-view know. | |
for (size_t i = 0; i < newChildrenVector.size(); ++i) | |
if (newChildrenVector[i]->platformLayer()) | |
newChildrenVector[i]->platformLayer()->setZValue(i); | |
} | |
if (m_changeMask & MaskLayerChange) { | |
// we can't paint here, because we don't know if the mask layer | |
// itself is ready... we'll have to wait till this layer tries to paint | |
setFlag(ItemClipsChildrenToShape, m_layer->maskLayer() || m_layer->masksToBounds()); | |
#ifndef QT_NO_GRAPHICSEFFECT | |
setGraphicsEffect(0); | |
if (m_layer->maskLayer()) { | |
if (GraphicsLayerQtImpl* mask = qobject_cast<GraphicsLayerQtImpl*>(m_layer->maskLayer()->platformLayer()->toGraphicsObject())) { | |
mask->m_maskEffect = new MaskEffectQt(this, mask); | |
setGraphicsEffect(mask->m_maskEffect.data()); | |
} | |
} | |
#endif | |
} | |
if (m_changeMask & SizeChange) { | |
if (m_layer->size() != m_state.size) { | |
prepareGeometryChange(); | |
m_size = QSizeF(m_layer->size().width(), m_layer->size().height()); | |
} | |
} | |
// FIXME: this is a hack, due to a probable QGraphicsScene bug when rapidly modifying the perspective | |
// but without this line we get graphic artifacts | |
if ((m_changeMask & ChildrenTransformChange) && m_state.childrenTransform != m_layer->childrenTransform()) | |
if (scene()) | |
scene()->update(); | |
if (m_changeMask & (ChildrenTransformChange | Preserves3DChange | TransformChange | AnchorPointChange | SizeChange | BackfaceVisibilityChange | PositionChange)) { | |
// due to the differences between the way WebCore handles transforms and the way Qt handles transforms, | |
// all these elements affect the transforms of all the descendants. | |
forceUpdateTransform = true; | |
} | |
if (m_changeMask & (ContentChange | DrawsContentChange | MaskLayerChange)) { | |
switch (m_pendingContent.contentType) { | |
case PixmapContentType: | |
update(); | |
setFlag(ItemHasNoContents, false); | |
break; | |
case MediaContentType: | |
setFlag(ItemHasNoContents, true); | |
m_pendingContent.mediaLayer.data()->setParentItem(this); | |
break; | |
case ColorContentType: | |
if (m_pendingContent.contentType != m_currentContent.contentType || m_pendingContent.contentsBackgroundColor != m_currentContent.contentsBackgroundColor) | |
update(); | |
m_state.drawsContent = false; | |
setFlag(ItemHasNoContents, false); | |
// we only use ItemUsesExtendedStyleOption for HTML content - colors don't gain much from that anyway | |
setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, false); | |
break; | |
case HTMLContentType: | |
if (m_pendingContent.contentType != m_currentContent.contentType) | |
update(); | |
if (!m_state.drawsContent && m_layer->drawsContent()) | |
update(); | |
setFlag(ItemHasNoContents, !m_layer->drawsContent()); | |
break; | |
} | |
} | |
if ((m_changeMask & OpacityChange) && m_state.opacity != m_layer->opacity() && !m_opacityAnimationRunning) | |
setOpacity(m_layer->opacity()); | |
if (m_changeMask & ContentsRectChange) { | |
const QRect rect(m_layer->contentsRect()); | |
if (m_state.contentsRect != rect) { | |
m_state.contentsRect = rect; | |
update(); | |
} | |
} | |
if ((m_changeMask & MasksToBoundsChange) | |
&& m_state.masksToBounds != m_layer->masksToBounds()) { | |
setFlag(QGraphicsItem::ItemClipsToShape, m_layer->masksToBounds()); | |
setFlag(QGraphicsItem::ItemClipsChildrenToShape, m_layer->masksToBounds()); | |
} | |
if ((m_changeMask & ContentsOpaqueChange) && m_state.contentsOpaque != m_layer->contentsOpaque()) | |
prepareGeometryChange(); | |
#ifndef QT_NO_GRAPHICSEFFECT | |
if (m_maskEffect) | |
m_maskEffect.data()->update(); | |
else | |
#endif | |
if (m_changeMask & DisplayChange) { | |
#ifndef QT_GRAPHICS_LAYER_NO_RECACHE_ON_DISPLAY_CHANGE | |
// Recache now: all the content is ready and we don't want to wait until the paint event. | |
// We only need to do this for HTML content, there's no point in caching directly composited | |
// content like images or solid rectangles. | |
if (m_pendingContent.contentType == HTMLContentType) | |
recache(m_pendingContent.regionToUpdate); | |
#endif | |
update(m_pendingContent.regionToUpdate.boundingRect()); | |
m_pendingContent.regionToUpdate = QRegion(); | |
} | |
if ((m_changeMask & BackgroundColorChange) && (m_pendingContent.backgroundColor != m_currentContent.backgroundColor)) | |
update(); | |
m_state.maskLayer = m_layer->maskLayer(); | |
m_state.pos = m_layer->position(); | |
m_state.anchorPoint = m_layer->anchorPoint(); | |
m_state.size = m_layer->size(); | |
m_state.transform = m_layer->transform(); | |
m_state.geoOrientation = m_layer->geometryOrientation(); | |
m_state.contentsOrientation =m_layer->contentsOrientation(); | |
m_state.opacity = m_layer->opacity(); | |
m_state.contentsRect = m_layer->contentsRect(); | |
m_state.preserves3D = m_layer->preserves3D(); | |
m_state.masksToBounds = m_layer->masksToBounds(); | |
m_state.drawsContent = m_layer->drawsContent(); | |
m_state.contentsOpaque = m_layer->contentsOpaque(); | |
m_state.backfaceVisibility = m_layer->backfaceVisibility(); | |
m_state.childrenTransform = m_layer->childrenTransform(); | |
m_currentContent.pixmap = m_pendingContent.pixmap; | |
m_currentContent.contentType = m_pendingContent.contentType; | |
m_currentContent.mediaLayer = m_pendingContent.mediaLayer; | |
m_currentContent.backgroundColor = m_pendingContent.backgroundColor; | |
m_currentContent.contentsBackgroundColor = m_pendingContent.contentsBackgroundColor; | |
m_pendingContent.regionToUpdate = QRegion(); | |
m_changeMask = NoChanges; | |
afterLayerChanges: | |
if (forceUpdateTransform) | |
updateTransform(); | |
if (!recursive) | |
return; | |
QList<QGraphicsItem*> children = childItems(); | |
if (m_state.maskLayer) | |
children.append(m_state.maskLayer->platformLayer()); | |
for (QList<QGraphicsItem*>::const_iterator it = children.begin(); it != children.end(); ++it) { | |
if (QGraphicsItem* item = *it) | |
if (GraphicsLayerQtImpl* layer = qobject_cast<GraphicsLayerQtImpl*>(item->toGraphicsObject())) | |
layer->flushChanges(true, forceUpdateTransform); | |
} | |
} | |
void GraphicsLayerQtImpl::notifyAnimationStarted() | |
{ | |
// WebCore notifies javascript when the animation starts | |
// here we're letting it know | |
m_layer->client()->notifyAnimationStarted(m_layer, WTF::currentTime()); | |
} | |
GraphicsLayerQt::GraphicsLayerQt(GraphicsLayerClient* client) | |
: GraphicsLayer(client) | |
, m_impl(PassOwnPtr<GraphicsLayerQtImpl>(new GraphicsLayerQtImpl(this))) | |
{ | |
} | |
GraphicsLayerQt::~GraphicsLayerQt() | |
{ | |
} | |
// this is the hook for WebCore compositor to know that Qt implements compositing with GraphicsLayerQt | |
PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client) | |
{ | |
return new GraphicsLayerQt(client); | |
} | |
// reimp from GraphicsLayer.h: Qt is top-down | |
GraphicsLayer::CompositingCoordinatesOrientation GraphicsLayer::compositingCoordinatesOrientation() | |
{ | |
return CompositingCoordinatesTopDown; | |
} | |
// 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 | |
void GraphicsLayerQt::setNeedsDisplay() | |
{ | |
m_impl->m_pendingContent.regionToUpdate = QRegion(QRect(QPoint(0, 0), QSize(size().width(), size().height()))); | |
m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setNeedsDisplayInRect(const FloatRect& rect) | |
{ | |
m_impl->m_pendingContent.regionToUpdate|= QRectF(rect).toAlignedRect(); | |
m_impl->notifyChange(GraphicsLayerQtImpl::DisplayChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setName(const String& name) | |
{ | |
m_impl->setObjectName(name); | |
GraphicsLayer::setName(name); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setParent(GraphicsLayer* layer) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange); | |
GraphicsLayer::setParent(layer); | |
} | |
// reimp from GraphicsLayer.h | |
bool GraphicsLayerQt::setChildren(const Vector<GraphicsLayer*>& children) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
return GraphicsLayer::setChildren(children); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::addChild(GraphicsLayer* layer) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
GraphicsLayer::addChild(layer); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::addChildAtIndex(GraphicsLayer* layer, int index) | |
{ | |
GraphicsLayer::addChildAtIndex(layer, index); | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::addChildAbove(GraphicsLayer* layer, GraphicsLayer* sibling) | |
{ | |
GraphicsLayer::addChildAbove(layer, sibling); | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::addChildBelow(GraphicsLayer* layer, GraphicsLayer* sibling) | |
{ | |
GraphicsLayer::addChildBelow(layer, sibling); | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
} | |
// reimp from GraphicsLayer.h | |
bool GraphicsLayerQt::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild) | |
{ | |
if (GraphicsLayer::replaceChild(oldChild, newChild)) { | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenChange); | |
return true; | |
} | |
return false; | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::removeFromParent() | |
{ | |
if (parent()) | |
m_impl->notifyChange(GraphicsLayerQtImpl::ParentChange); | |
GraphicsLayer::removeFromParent(); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setMaskLayer(GraphicsLayer* value) | |
{ | |
if (value == maskLayer()) | |
return; | |
GraphicsLayer::setMaskLayer(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::MaskLayerChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setPosition(const FloatPoint& value) | |
{ | |
if (value == position()) | |
return; | |
GraphicsLayer::setPosition(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::PositionChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setAnchorPoint(const FloatPoint3D& value) | |
{ | |
if (value == anchorPoint()) | |
return; | |
GraphicsLayer::setAnchorPoint(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::AnchorPointChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setSize(const FloatSize& value) | |
{ | |
if (value == size()) | |
return; | |
GraphicsLayer::setSize(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::SizeChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setTransform(const TransformationMatrix& value) | |
{ | |
if (value == transform()) | |
return; | |
GraphicsLayer::setTransform(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::TransformChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setChildrenTransform(const TransformationMatrix& value) | |
{ | |
if (value == childrenTransform()) | |
return; | |
GraphicsLayer::setChildrenTransform(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenTransformChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setPreserves3D(bool value) | |
{ | |
if (value == preserves3D()) | |
return; | |
GraphicsLayer::setPreserves3D(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::Preserves3DChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setMasksToBounds(bool value) | |
{ | |
if (value == masksToBounds()) | |
return; | |
GraphicsLayer::setMasksToBounds(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::MasksToBoundsChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setDrawsContent(bool value) | |
{ | |
if (value == drawsContent()) | |
return; | |
m_impl->notifyChange(GraphicsLayerQtImpl::DrawsContentChange); | |
GraphicsLayer::setDrawsContent(value); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setBackgroundColor(const Color& value) | |
{ | |
if (value == m_impl->m_pendingContent.backgroundColor) | |
return; | |
m_impl->m_pendingContent.backgroundColor = value; | |
GraphicsLayer::setBackgroundColor(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::clearBackgroundColor() | |
{ | |
if (!m_impl->m_pendingContent.backgroundColor.isValid()) | |
return; | |
m_impl->m_pendingContent.backgroundColor = QColor(); | |
GraphicsLayer::clearBackgroundColor(); | |
m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setContentsOpaque(bool value) | |
{ | |
if (value == contentsOpaque()) | |
return; | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOpaqueChange); | |
GraphicsLayer::setContentsOpaque(value); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setBackfaceVisibility(bool value) | |
{ | |
if (value == backfaceVisibility()) | |
return; | |
GraphicsLayer::setBackfaceVisibility(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::BackfaceVisibilityChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setOpacity(float value) | |
{ | |
if (value == opacity()) | |
return; | |
GraphicsLayer::setOpacity(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setContentsRect(const IntRect& value) | |
{ | |
if (value == contentsRect()) | |
return; | |
GraphicsLayer::setContentsRect(value); | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentsRectChange); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setContentsToImage(Image* image) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); | |
m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType; | |
GraphicsLayer::setContentsToImage(image); | |
if (image) { | |
QPixmap* pxm = image->nativeImageForCurrentFrame(); | |
if (pxm) { | |
m_impl->m_pendingContent.pixmap = *pxm; | |
m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::PixmapContentType; | |
return; | |
} | |
} | |
m_impl->m_pendingContent.pixmap = QPixmap(); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setContentsBackgroundColor(const Color& color) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); | |
m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::ColorContentType; | |
m_impl->m_pendingContent.contentsBackgroundColor = QColor(color); | |
GraphicsLayer::setContentsBackgroundColor(color); | |
} | |
void GraphicsLayerQt::setContentsToMedia(PlatformLayer* media) | |
{ | |
if (media) { | |
m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::MediaContentType; | |
m_impl->m_pendingContent.mediaLayer = media->toGraphicsObject(); | |
} else | |
m_impl->m_pendingContent.contentType = GraphicsLayerQtImpl::HTMLContentType; | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentChange); | |
GraphicsLayer::setContentsToMedia(media); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setGeometryOrientation(CompositingCoordinatesOrientation orientation) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::GeometryOrientationChange); | |
GraphicsLayer::setGeometryOrientation(orientation); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::setContentsOrientation(CompositingCoordinatesOrientation orientation) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOrientationChange); | |
GraphicsLayer::setContentsOrientation(orientation); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::distributeOpacity(float o) | |
{ | |
m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange); | |
m_impl->m_state.distributeOpacity = true; | |
} | |
// reimp from GraphicsLayer.h | |
float GraphicsLayerQt::accumulatedOpacity() const | |
{ | |
return m_impl->effectiveOpacity(); | |
} | |
// reimp from GraphicsLayer.h | |
void GraphicsLayerQt::syncCompositingState() | |
{ | |
m_impl->flushChanges(); | |
GraphicsLayer::syncCompositingState(); | |
} | |
// reimp from GraphicsLayer.h | |
NativeLayer GraphicsLayerQt::nativeLayer() const | |
{ | |
return m_impl.get(); | |
} | |
// reimp from GraphicsLayer.h | |
PlatformLayer* GraphicsLayerQt::platformLayer() const | |
{ | |
return m_impl.get(); | |
} | |
// now we start dealing with WebCore animations translated to Qt animations | |
template <typename T> | |
struct KeyframeValueQt { | |
TimingFunction timingFunction; | |
T value; | |
}; | |
// we copy this from the AnimationBase.cpp | |
static inline double solveEpsilon(double duration) | |
{ | |
return 1.0 / (200.0 * duration); | |
} | |
static inline double solveCubicBezierFunction(qreal p1x, qreal p1y, qreal p2x, qreal p2y, double t, double duration) | |
{ | |
UnitBezier bezier(p1x, p1y, p2x, p2y); | |
return bezier.solve(t, solveEpsilon(duration)); | |
} | |
// 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 | |
// 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 | |
static inline qreal applyTimingFunction(const TimingFunction& timingFunction, qreal progress, double duration) | |
{ | |
if (timingFunction.type() == LinearTimingFunction) | |
return progress; | |
if (timingFunction.type() == CubicBezierTimingFunction) { | |
return solveCubicBezierFunction(timingFunction.x1(), | |
timingFunction.y1(), | |
timingFunction.x2(), | |
timingFunction.y2(), | |
double(progress), double(duration) / 1000); | |
} | |
return progress; | |
} | |
// helper functions to safely get a value out of WebCore's AnimationValue* | |
static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, TransformOperations& transformOperations) | |
{ | |
transformOperations = TransformOperations(); | |
if (!animationValue) | |
return; | |
if (const TransformOperations* ops = static_cast<const TransformAnimationValue*>(animationValue)->value()) | |
transformOperations = *ops; | |
} | |
static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, qreal& realValue) | |
{ | |
realValue = animationValue ? static_cast<const FloatAnimationValue*>(animationValue)->value() : 0; | |
} | |
#ifndef QT_NO_ANIMATION | |
// we put a bit of the functionality in a base class to allow casting and to save some code size | |
class AnimationQtBase : public QAbstractAnimation { | |
public: | |
AnimationQtBase(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) | |
: QAbstractAnimation(0) | |
, m_layer(layer) | |
, m_boxSize(boxSize) | |
, m_duration(anim->duration() * 1000) | |
, m_isAlternate(anim->direction() == Animation::AnimationDirectionAlternate) | |
, m_webkitPropertyID(values.property()) | |
, m_webkitAnimation(anim) | |
, m_keyframesName(name) | |
, m_fillsForwards(false) | |
{ | |
} | |
virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) | |
{ | |
QAbstractAnimation::updateState(newState, oldState); | |
// for some reason I have do this asynchronously - or the animation won't work | |
if (newState == Running && oldState == Stopped && m_layer.data()) | |
m_layer.data()->notifyAnimationStartedAsync(); | |
} | |
virtual int duration() const { return m_duration; } | |
QWeakPointer<GraphicsLayerQtImpl> m_layer; | |
IntSize m_boxSize; | |
int m_duration; | |
bool m_isAlternate; | |
AnimatedPropertyID m_webkitPropertyID; | |
// we might need this in case the same animation is added again (i.e. resumed by WebCore) | |
const Animation* m_webkitAnimation; | |
QString m_keyframesName; | |
bool m_fillsForwards; | |
}; | |
// we'd rather have a templatized QAbstractAnimation than QPropertyAnimation / QVariantAnimation; | |
// Since we know the types that we're dealing with, the QObject/QProperty/QVariant abstraction | |
// buys us very little in this case, for too much overhead | |
template <typename T> | |
class AnimationQt : public AnimationQtBase { | |
public: | |
AnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) | |
:AnimationQtBase(layer, values, boxSize, anim, name) | |
{ | |
// copying those WebCore structures is not trivial, we have to do it like this | |
for (size_t i = 0; i < values.size(); ++i) { | |
const AnimationValue* animationValue = values.at(i); | |
KeyframeValueQt<T> keyframeValue; | |
if (animationValue->timingFunction()) | |
keyframeValue.timingFunction = *animationValue->timingFunction(); | |
else | |
keyframeValue.timingFunction = anim->timingFunction(); | |
webkitAnimationToQtAnimationValue(animationValue, keyframeValue.value); | |
m_keyframeValues[animationValue->keyTime()] = keyframeValue; | |
} | |
} | |
protected: | |
// this is the part that differs between animated properties | |
virtual void applyFrame(const T& fromValue, const T& toValue, qreal progress) = 0; | |
virtual void updateCurrentTime(int currentTime) | |
{ | |
if (!m_layer) | |
return; | |
qreal progress = qreal(currentLoopTime()) / duration(); | |
if (m_isAlternate && currentLoop()%2) | |
progress = 1-progress; | |
if (m_keyframeValues.isEmpty()) | |
return; | |
// we find the current from-to keyframes in our little map | |
typename QMap<qreal, KeyframeValueQt<T> >::iterator it = m_keyframeValues.find(progress); | |
// we didn't find an exact match, we try the closest match (lower bound) | |
if (it == m_keyframeValues.end()) | |
it = m_keyframeValues.lowerBound(progress)-1; | |
// we didn't find any match - we use the first keyframe | |
if (it == m_keyframeValues.end()) | |
it = m_keyframeValues.begin(); | |
typename QMap<qreal, KeyframeValueQt<T> >::iterator it2 = it+1; | |
if (it2 == m_keyframeValues.end()) | |
it2 = it; | |
const KeyframeValueQt<T>& fromKeyframe = it.value(); | |
const KeyframeValueQt<T>& toKeyframe = it2.value(); | |
const TimingFunction& timingFunc = fromKeyframe.timingFunction; | |
const T& fromValue = fromKeyframe.value; | |
const T& toValue = toKeyframe.value; | |
// now we have a source keyframe, origin keyframe and a timing function | |
// we can now process the progress and apply the frame | |
progress = (!progress || progress == 1 || it.key() == it2.key()) | |
? progress | |
: applyTimingFunction(timingFunc, (progress - it.key()) / (it2.key() - it.key()), duration()); | |
applyFrame(fromValue, toValue, progress); | |
} | |
QMap<qreal, KeyframeValueQt<T> > m_keyframeValues; | |
}; | |
class TransformAnimationQt : public AnimationQt<TransformOperations> { | |
public: | |
TransformAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) | |
: AnimationQt<TransformOperations>(layer, values, boxSize, anim, name) | |
{ | |
} | |
~TransformAnimationQt() | |
{ | |
if (m_fillsForwards) | |
setCurrentTime(1); | |
} | |
// the idea is that we let WebCore manage the transform-operations | |
// and Qt just manages the animation heartbeat and the bottom-line QTransform | |
// we get the performance not by using QTransform instead of TransformationMatrix, but by proper caching of | |
// items that are expensive for WebCore to render. We want the rest to be as close to WebCore's idea as possible. | |
virtual void applyFrame(const TransformOperations& sourceOperations, const TransformOperations& targetOperations, qreal progress) | |
{ | |
TransformationMatrix transformMatrix; | |
bool validTransformLists = true; | |
const int sourceOperationCount = sourceOperations.size(); | |
if (sourceOperationCount) { | |
if (targetOperations.size() != sourceOperationCount) | |
validTransformLists = false; | |
else | |
for (size_t j = 0; j < sourceOperationCount && validTransformLists; ++j) | |
if (!sourceOperations.operations()[j]->isSameType(*targetOperations.operations()[j])) | |
validTransformLists = false; | |
} | |
if (validTransformLists) { | |
for (size_t i = 0; i < targetOperations.size(); ++i) | |
targetOperations.operations()[i]->blend(sourceOperations.at(i), progress)->apply(transformMatrix, m_boxSize); | |
} else { | |
targetOperations.apply(m_boxSize, transformMatrix); | |
transformMatrix.blend(m_sourceMatrix, progress); | |
} | |
m_layer.data()->m_layer->setTransform(transformMatrix); | |
// We force the actual opacity change, otherwise it would be ignored because of the animation. | |
m_layer.data()->setBaseTransform(transformMatrix); | |
} | |
virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) | |
{ | |
AnimationQtBase::updateState(newState, oldState); | |
if (!m_layer) | |
return; | |
m_layer.data()->flushChanges(true); | |
// to increase FPS, we use a less accurate caching mechanism while animation is going on | |
// this is a UX choice that should probably be customizable | |
if (newState == QAbstractAnimation::Running) { | |
m_sourceMatrix = m_layer.data()->m_layer->transform(); | |
m_layer.data()->m_transformAnimationRunning = true; | |
} else if (newState == QAbstractAnimation::Stopped) { | |
// We update the transform back to the default. This already takes fill-modes into account. | |
m_layer.data()->m_transformAnimationRunning = false; | |
if (m_layer && m_layer.data()->m_layer) | |
m_layer.data()->setBaseTransform(m_layer.data()->m_layer->transform()); | |
} | |
} | |
TransformationMatrix m_sourceMatrix; | |
}; | |
class OpacityAnimationQt : public AnimationQt<qreal> { | |
public: | |
OpacityAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) | |
: AnimationQt<qreal>(layer, values, boxSize, anim, name) | |
{ | |
} | |
~OpacityAnimationQt() | |
{ | |
if (m_fillsForwards) | |
setCurrentTime(1); | |
} | |
virtual void applyFrame(const qreal& fromValue, const qreal& toValue, qreal progress) | |
{ | |
qreal opacity = qBound(qreal(0), fromValue + (toValue-fromValue)*progress, qreal(1)); | |
// FIXME: this is a hack, due to a probable QGraphicsScene bug. | |
// Without this the opacity change doesn't always have immediate effect. | |
if (m_layer.data()->scene() && !m_layer.data()->opacity() && opacity) | |
m_layer.data()->scene()->update(); | |
m_layer.data()->m_layer->setOpacity(opacity); | |
// We force the actual opacity change, otherwise it would be ignored because of the animation. | |
m_layer.data()->setOpacity(opacity); | |
} | |
virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) | |
{ | |
QAbstractAnimation::updateState(newState, oldState); | |
if (m_layer) | |
m_layer.data()->m_opacityAnimationRunning = (newState == QAbstractAnimation::Running); | |
// If stopped, we update the opacity back to the default. This already takes fill-modes into account. | |
if (newState == Stopped) | |
if (m_layer && m_layer.data()->m_layer) | |
m_layer.data()->setOpacity(m_layer.data()->m_layer->opacity()); | |
} | |
}; | |
bool GraphicsLayerQt::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) | |
{ | |
if (!anim->duration() || !anim->iterationCount()) | |
return false; | |
AnimationQtBase* newAnim = 0; | |
// fixed: we might already have the Qt animation object associated with this WebCore::Animation object | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
if (*it) { | |
AnimationQtBase* curAnimation = static_cast<AnimationQtBase*>(it->data()); | |
if (curAnimation && curAnimation->m_webkitAnimation == anim) | |
newAnim = curAnimation; | |
} | |
} | |
if (!newAnim) { | |
switch (values.property()) { | |
case AnimatedPropertyOpacity: | |
newAnim = new OpacityAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName); | |
break; | |
case AnimatedPropertyWebkitTransform: | |
newAnim = new TransformAnimationQt(m_impl.get(), values, boxSize, anim, keyframesName); | |
break; | |
default: | |
return false; | |
} | |
// we make sure WebCore::Animation and QAnimation are on the same terms | |
newAnim->setLoopCount(anim->iterationCount()); | |
newAnim->m_fillsForwards = anim->fillsForwards(); | |
m_impl->m_animations.append(QWeakPointer<QAbstractAnimation>(newAnim)); | |
QObject::connect(&m_impl->m_suspendTimer, SIGNAL(timeout()), newAnim, SLOT(resume())); | |
} | |
// flush now or flicker... | |
m_impl->flushChanges(false); | |
// when fill-mode is backwards/both, we set the value to 0 before the delay takes place | |
if (anim->fillsBackwards()) | |
newAnim->setCurrentTime(0); | |
newAnim->start(); | |
// we synchronize the animation's clock to WebCore's timeOffset | |
newAnim->setCurrentTime(timeOffset * 1000); | |
// we don't need to manage the animation object's lifecycle: | |
// WebCore would call removeAnimations when it's time to delete. | |
return true; | |
} | |
void GraphicsLayerQt::removeAnimationsForProperty(AnimatedPropertyID id) | |
{ | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
if (*it) { | |
AnimationQtBase* anim = static_cast<AnimationQtBase*>(it->data()); | |
if (anim && anim->m_webkitPropertyID == id) { | |
// We need to stop the animation right away, or it might flicker before it's deleted. | |
anim->stop(); | |
anim->deleteLater(); | |
it = m_impl->m_animations.erase(it); | |
--it; | |
} | |
} | |
} | |
} | |
void GraphicsLayerQt::removeAnimationsForKeyframes(const String& name) | |
{ | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
if (*it) { | |
AnimationQtBase* anim = static_cast<AnimationQtBase*>((*it).data()); | |
if (anim && anim->m_keyframesName == QString(name)) { | |
// We need to stop the animation right away, or it might flicker before it's deleted. | |
anim->stop(); | |
anim->deleteLater(); | |
it = m_impl->m_animations.erase(it); | |
--it; | |
} | |
} | |
} | |
} | |
void GraphicsLayerQt::pauseAnimation(const String& name, double timeOffset) | |
{ | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
if (!(*it)) | |
continue; | |
AnimationQtBase* anim = static_cast<AnimationQtBase*>((*it).data()); | |
if (anim && anim->m_keyframesName == QString(name)) { | |
// we synchronize the animation's clock to WebCore's timeOffset | |
anim->setCurrentTime(timeOffset * 1000); | |
anim->pause(); | |
} | |
} | |
} | |
void GraphicsLayerQt::suspendAnimations(double time) | |
{ | |
if (m_impl->m_suspendTimer.isActive()) { | |
m_impl->m_suspendTimer.stop(); | |
m_impl->m_suspendTimer.start(time * 1000); | |
} else { | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
QAbstractAnimation* anim = it->data(); | |
if (anim) | |
anim->pause(); | |
} | |
} | |
} | |
void GraphicsLayerQt::resumeAnimations() | |
{ | |
if (m_impl->m_suspendTimer.isActive()) { | |
m_impl->m_suspendTimer.stop(); | |
for (QList<QWeakPointer<QAbstractAnimation> >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { | |
QAbstractAnimation* anim = (*it).data(); | |
if (anim) | |
anim->resume(); | |
} | |
} | |
} | |
#endif // QT_NO_ANIMATION | |
} | |
#include <GraphicsLayerQt.moc> |