/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include namespace WebCore { 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, 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), ParentChange = (1L << 20), DistributesOpacityChange = (1L << 21) }; // the compositor lets us special-case images and colors, so we try to do so enum StaticContentType { HTMLContentType, PixmapContentType, ColorContentType}; 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 void setBaseTransform(const QTransform&); void drawContents(QPainter*, const QRectF&, bool mask = false); // let the compositor-API tell us which properties were changed void notifyChange(ChangeMask); // 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); public slots: // we need to notify the client (aka the layer compositor) when the animation actually starts void notifyAnimationStarted(); public: GraphicsLayerQt* m_layer; QTransform m_baseTransfom; bool m_transformAnimationRunning; bool m_opacityAnimationRunning; struct ContentData { QPixmap pixmap; QRegion regionToUpdate; bool updateAll; QColor contentsBackgroundColor; QColor backgroundColor; StaticContentType contentType; float opacity; ContentData() : updateAll(false) , contentType(HTMLContentType) , opacity(1.f) { } }; ContentData m_pendingContent; ContentData m_currentContent; int m_changeMask; QSizeF m_size; QList > m_animations; 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), preserves3D(false), masksToBounds(false), drawsContent(false), contentsOpaque(false), backfaceVisibility(false), distributeOpacity(false) { } } m_state; }; GraphicsLayerQtImpl::GraphicsLayerQtImpl(GraphicsLayerQt* newLayer) : QGraphicsObject(0) , m_layer(newLayer) , m_transformAnimationRunning(false) , m_changeMask(NoChanges) { // better to calculate the exposed rect in QGraphicsView than over-render in WebCore // FIXME: test different approaches setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); // we use graphics-view for compositing, not for interactivity setAcceptedMouseButtons(Qt::NoButton); setEnabled(false); // we'll set the cache when we know what's going on setCacheMode(NoCache); } GraphicsLayerQtImpl::~GraphicsLayerQtImpl() { // the compositor manages item lifecycle - we don't want the graphics-view // system to automatically delete our items const QList children = childItems(); for (QList::const_iterator it = children.begin(); it != children.end(); ++it) { if (QGraphicsItem* item = *it) { if (scene()) scene()->removeItem(item); item->setParentItem(0); } } // we do, however, own the animations... for (QList >::iterator it = m_animations.begin(); it != m_animations.end(); ++it) if (QAbstractAnimation* anim = it->data()) delete anim; } void GraphicsLayerQtImpl::setBaseTransform(const QTransform& transform) { if (!m_layer) return; // webkit has relative-to-size originPoint, graphics-view has a pixel originPoint // here we convert QPointF originTranslate( m_layer->anchorPoint().x() * m_layer->size().width(), m_layer->anchorPoint().y() * m_layer->size().height()); resetTransform(); // we have to manage this ourselves because QGraphicsView's transformOrigin is incomplete translate(originTranslate.x(), originTranslate.y()); setTransform(transform, true); translate(-originTranslate.x(), -originTranslate.y()); m_baseTransfom = transform; } 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 == 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_state.maskLayer && m_state.maskLayer->platformLayer()) { // FIXME: see if this is better done somewhere else GraphicsLayerQtImpl* otherMask = static_cast(m_state.maskLayer->platformLayer()); otherMask->flushChanges(true); // CSS3 mask and QGraphicsOpacityEffect are the same thing! we just need to convert... // The conversion is as fast as we can make it - we render the layer once and send it to the QGraphicsOpacityEffect if (!graphicsEffect()) { QPixmap mask(QSize(m_state.maskLayer->size().width(), m_state.maskLayer->size().height())); mask.fill(Qt::transparent); { QPainter p(&mask); p.setRenderHints(painter->renderHints(), true); p.setCompositionMode(QPainter::CompositionMode_Source); static_cast(m_state.maskLayer->platformLayer())->drawContents(&p, option->exposedRect, true); } QGraphicsOpacityEffect* opacityEffect = new QGraphicsOpacityEffect(this); opacityEffect->setOpacity(1); opacityEffect->setOpacityMask(QBrush(mask)); setGraphicsEffect(opacityEffect); } } drawContents(painter, option->exposedRect); } void GraphicsLayerQtImpl::drawContents(QPainter* painter, const QRectF& r, bool mask) { QRect rect = r.toAlignedRect(); if (m_currentContent.contentType != HTMLContentType && !m_state.contentsRect.isEmpty()) rect = rect.intersected(m_state.contentsRect); if (m_currentContent.backgroundColor.isValid()) painter->fillRect(r, QColor(m_currentContent.backgroundColor)); if (!rect.isEmpty()) { switch (m_currentContent.contentType) { case PixmapContentType: // we have to scale the image to the contentsRect // FIXME: a better way would probably be drawPixmap with a src/target rect painter->drawPixmap(rect.topLeft(), m_currentContent.pixmap.scaled(m_state.contentsRect.size()), r); break; case ColorContentType: painter->fillRect(rect, m_currentContent.contentsBackgroundColor); break; default: if (m_state.drawsContent) { // this is the "expensive" bit. we try to minimize calls to this // neck of the woods by proper caching GraphicsContext gc(painter); m_layer->paintGraphicsLayerContents(gc, rect); } break; } } } void GraphicsLayerQtImpl::notifyChange(ChangeMask changeMask) { if (!this) return; m_changeMask |= changeMask; if (m_layer->client()) m_layer->client()->notifySyncRequired(m_layer); } void GraphicsLayerQtImpl::flushChanges(bool recursive) { // 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 newChildren; const Vector newChildrenVector = (m_layer->children()); newChildren.reserve(newChildrenVector.size()); for (size_t i = 0; i < newChildrenVector.size(); ++i) newChildren.insert(newChildrenVector[i]->platformLayer()); const QSet currentChildren = childItems().toSet(); const QSet childrenToAdd = newChildren - currentChildren; const QSet childrenToRemove = currentChildren - newChildren; for (QSet::const_iterator it = childrenToAdd.begin(); it != childrenToAdd.end(); ++it) { if (QGraphicsItem* w = *it) w->setParentItem(this); } for (QSet::const_iterator it = childrenToRemove.begin(); it != childrenToRemove.end(); ++it) { if (QGraphicsItem* w = *it) 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 setGraphicsEffect(0); if (m_layer->maskLayer()) setFlag(ItemClipsChildrenToShape, true); else setFlag(ItemClipsChildrenToShape, m_layer->masksToBounds()); update(); } if ((m_changeMask & PositionChange) && (m_layer->position() != m_state.pos)) setPos(m_layer->position().x(), m_layer->position().y()); if (m_changeMask & SizeChange) { if (m_layer->size() != m_state.size) { prepareGeometryChange(); m_size = QSizeF(m_layer->size().width(), m_layer->size().height()); } } if (m_changeMask & (TransformChange | AnchorPointChange | SizeChange)) { // since we convert a percentage-based origin-point to a pixel-based one, // the anchor-point, transform and size from WebCore all affect the one // that we give Qt if (m_state.transform != m_layer->transform() || m_state.anchorPoint != m_layer->anchorPoint() || m_state.size != m_layer->size()) setBaseTransform(QTransform(m_layer->transform())); } if (m_changeMask & (ContentChange | DrawsContentChange)) { switch (m_pendingContent.contentType) { case PixmapContentType: // we need cache even for images, because they need to be resized // to the contents rect. maybe this can be optimized though setCacheMode(m_transformAnimationRunning ? ItemCoordinateCache : DeviceCoordinateCache); update(); setFlag(ItemHasNoContents, false); break; case ColorContentType: // no point in caching a solid-color rectangle setCacheMode(QGraphicsItem::NoCache); if (m_pendingContent.contentType != m_currentContent.contentType || m_pendingContent.contentsBackgroundColor != m_currentContent.contentsBackgroundColor) update(); m_state.drawsContent = false; setFlag(ItemHasNoContents, false); break; case HTMLContentType: if (m_pendingContent.contentType != m_currentContent.contentType) update(); if (!m_state.drawsContent && m_layer->drawsContent()) update(); if (m_layer->drawsContent()) setCacheMode(m_transformAnimationRunning ? ItemCoordinateCache : DeviceCoordinateCache); else setCacheMode(NoCache); setFlag(ItemHasNoContents, !m_layer->drawsContent()); break; } } if ((m_changeMask & OpacityChange) && m_state.opacity != m_layer->opacity()) 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(); if (m_changeMask & DisplayChange) update(m_pendingContent.regionToUpdate.boundingRect()); if ((m_changeMask & BackgroundColorChange) && (m_pendingContent.backgroundColor != m_currentContent.backgroundColor)) update(); // FIXME: the following flags are currently not handled, as they don't have a clear test or are in low priority // GeometryOrientationChange, ContentsOrientationChange, BackfaceVisibilityChange, ChildrenTransformChange 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_currentContent.pixmap = m_pendingContent.pixmap; m_currentContent.contentType = m_pendingContent.contentType; m_currentContent.backgroundColor = m_pendingContent.backgroundColor; m_currentContent.regionToUpdate |= m_pendingContent.regionToUpdate; m_currentContent.contentsBackgroundColor = m_pendingContent.contentsBackgroundColor; m_pendingContent.regionToUpdate = QRegion(); m_changeMask = NoChanges; afterLayerChanges: if (!recursive) return; const QList children = childItems(); for (QList::const_iterator it = children.begin(); it != children.end(); ++it) { if (QGraphicsItem* item = *it) if (GraphicsLayerQtImpl* layer = qobject_cast(item->toGraphicsObject())) layer->flushChanges(true); } } 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(new GraphicsLayerQtImpl(this))) { } GraphicsLayerQt::~GraphicsLayerQt() { } // this is the hook for WebCore compositor to know that Qt implements compositing with GraphicsLayerQt PassOwnPtr 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& r) { m_impl->m_pendingContent.regionToUpdate|= QRectF(r).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& 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* layer) { GraphicsLayer::setMaskLayer(layer); m_impl->notifyChange(GraphicsLayerQtImpl::MaskLayerChange); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setPosition(const FloatPoint& p) { if (position() != p) m_impl->notifyChange(GraphicsLayerQtImpl::PositionChange); GraphicsLayer::setPosition(p); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setAnchorPoint(const FloatPoint3D& p) { if (anchorPoint() != p) m_impl->notifyChange(GraphicsLayerQtImpl::AnchorPointChange); GraphicsLayer::setAnchorPoint(p); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setSize(const FloatSize& size) { if (this->size() != size) m_impl->notifyChange(GraphicsLayerQtImpl::SizeChange); GraphicsLayer::setSize(size); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setTransform(const TransformationMatrix& t) { if (!m_impl->m_transformAnimationRunning && transform() != t) m_impl->notifyChange(GraphicsLayerQtImpl::TransformChange); GraphicsLayer::setTransform(t); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setChildrenTransform(const TransformationMatrix& t) { GraphicsLayer::setChildrenTransform(t); m_impl->notifyChange(GraphicsLayerQtImpl::ChildrenTransformChange); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setPreserves3D(bool b) { if (b != preserves3D()); m_impl->notifyChange(GraphicsLayerQtImpl::Preserves3DChange); GraphicsLayer::setPreserves3D(b); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setMasksToBounds(bool b) { GraphicsLayer::setMasksToBounds(b); m_impl->notifyChange(GraphicsLayerQtImpl::MasksToBoundsChange); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setDrawsContent(bool b) { m_impl->notifyChange(GraphicsLayerQtImpl::DrawsContentChange); GraphicsLayer::setDrawsContent(b); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setBackgroundColor(const Color& c) { m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); m_impl->m_pendingContent.backgroundColor = c; GraphicsLayer::setBackgroundColor(c); } // reimp from GraphicsLayer.h void GraphicsLayerQt::clearBackgroundColor() { m_impl->m_pendingContent.backgroundColor = QColor(); m_impl->notifyChange(GraphicsLayerQtImpl::BackgroundColorChange); GraphicsLayer::clearBackgroundColor(); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setContentsOpaque(bool b) { m_impl->notifyChange(GraphicsLayerQtImpl::ContentsOpaqueChange); GraphicsLayer::setContentsOpaque(b); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setBackfaceVisibility(bool b) { m_impl->notifyChange(GraphicsLayerQtImpl::BackfaceVisibilityChange); GraphicsLayer::setBackfaceVisibility(b); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setOpacity(float o) { if (!m_impl->m_opacityAnimationRunning && opacity() != o) m_impl->notifyChange(GraphicsLayerQtImpl::OpacityChange); GraphicsLayer::setOpacity(o); } // reimp from GraphicsLayer.h void GraphicsLayerQt::setContentsRect(const IntRect& r) { m_impl->notifyChange(GraphicsLayerQtImpl::ContentsRectChange); GraphicsLayer::setContentsRect(r); } // 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); } // 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 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, int 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; const TransformOperations* ops = static_cast(animationValue)->value(); if (ops) transformOperations = *ops; } static void webkitAnimationToQtAnimationValue(const AnimationValue* animationValue, qreal& realValue) { realValue = animationValue ? static_cast(animationValue)->value() : 0; } // 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_keyframesName(name) { } 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) QTimer::singleShot(0, m_layer.data(), SLOT(notifyAnimationStarted())); } virtual int duration() const { return m_duration; } QWeakPointer m_layer; IntSize m_boxSize; int m_duration; bool m_isAlternate; AnimatedPropertyID m_webkitPropertyID; QString m_keyframesName; }; // 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 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 keyframeValue; if (animationValue->timingFunction()) keyframeValue.timingFunction = *animationValue->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 >::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 >::iterator it2 = it+1; if (it2 == m_keyframeValues.end()) it2 = m_keyframeValues.begin(); const KeyframeValueQt& fromKeyframe = it.value(); const KeyframeValueQt& 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 qreal normalizedProgress = (it.key() == it2.key()) ? 0 : (progress - it.key()) / (it2.key() - it.key()); normalizedProgress = applyTimingFunction(timingFunc, normalizedProgress, duration() / 1000); applyFrame(fromValue, toValue, normalizedProgress); } QMap > m_keyframeValues; }; class TransformAnimationQt : public AnimationQt { public: TransformAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) : AnimationQt(layer, values, boxSize, anim, name) { } ~TransformAnimationQt() { // this came up during the compositing/animation LayoutTests // when the animation dies, the transform has to go back to default if (m_layer) m_layer.data()->setBaseTransform(QTransform(m_layer.data()->m_layer->transform())); } // 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; // this looks simple but is really tricky to get right. Use caution. for (size_t i = 0; i < targetOperations.size(); ++i) targetOperations.operations()[i]->blend(sourceOperations.at(i), progress)->apply(transformMatrix, m_boxSize); m_layer.data()->setBaseTransform(QTransform(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_layer.data()->m_transformAnimationRunning = true; if (m_layer.data()->cacheMode() == QGraphicsItem::DeviceCoordinateCache) m_layer.data()->setCacheMode(QGraphicsItem::ItemCoordinateCache); } else { m_layer.data()->m_transformAnimationRunning = false; if (m_layer.data()->cacheMode() == QGraphicsItem::ItemCoordinateCache) m_layer.data()->setCacheMode(QGraphicsItem::DeviceCoordinateCache); } } }; class OpacityAnimationQt : public AnimationQt { public: OpacityAnimationQt(GraphicsLayerQtImpl* layer, const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const QString & name) : AnimationQt(layer, values, boxSize, anim, name) { } virtual void applyFrame(const qreal& fromValue, const qreal& toValue, qreal progress) { m_layer.data()->setOpacity(qMin(qMax(fromValue + (toValue-fromValue)*progress, 0), 1)); } virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { QAbstractAnimation::updateState(newState, oldState); if (m_layer) m_layer.data()->m_opacityAnimationRunning = (newState == QAbstractAnimation::Running); } }; bool GraphicsLayerQt::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { if (!anim->duration() || !anim->iterationCount()) return false; QAbstractAnimation* 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()); m_impl->m_animations.append(QWeakPointer(newAnim)); QObject::connect(&m_impl->m_suspendTimer, SIGNAL(timeout()), newAnim, SLOT(resume())); timeOffset += anim->delay(); // flush now or flicker... m_impl->flushChanges(false); if (timeOffset) QTimer::singleShot(timeOffset * 1000, newAnim, SLOT(start())); else newAnim->start(); QObject::connect(newAnim, SIGNAL(finished()), newAnim, SLOT(deleteLater())); return true; } void GraphicsLayerQt::removeAnimationsForProperty(AnimatedPropertyID id) { for (QList >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { if (*it) { AnimationQtBase* anim = static_cast(it->data()); if (anim && anim->m_webkitPropertyID == id) { delete anim; it = m_impl->m_animations.erase(it); --it; } } } } void GraphicsLayerQt::removeAnimationsForKeyframes(const String& name) { for (QList >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { if (*it) { AnimationQtBase* anim = static_cast((*it).data()); if (anim && anim->m_keyframesName == QString(name)) { (*it).data()->deleteLater(); it = m_impl->m_animations.erase(it); --it; } } } } void GraphicsLayerQt::pauseAnimation(const String& name, double timeOffset) { for (QList >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { if (*it) { AnimationQtBase* anim = static_cast((*it).data()); if (anim && anim->m_keyframesName == QString(name)) QTimer::singleShot(timeOffset * 1000, anim, SLOT(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 >::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 >::iterator it = m_impl->m_animations.begin(); it != m_impl->m_animations.end(); ++it) { QAbstractAnimation* anim = (*it).data(); if (anim) anim->resume(); } } } } #include