diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source/engine/animationnodes | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'slideshow/source/engine/animationnodes')
32 files changed, 4673 insertions, 0 deletions
diff --git a/slideshow/source/engine/animationnodes/animationaudionode.cxx b/slideshow/source/engine/animationnodes/animationaudionode.cxx new file mode 100644 index 000000000..c6eb61d17 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationaudionode.cxx @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/NoSupportException.hpp> + +#include <eventqueue.hxx> +#include "animationaudionode.hxx" +#include <delayevent.hxx> + +using namespace com::sun::star; + +namespace slideshow::internal { + +AnimationAudioNode::AnimationAudioNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : BaseNode( xNode, rParent, rContext ), + mxAudioNode( xNode, uno::UNO_QUERY_THROW ), + maSoundURL(), + mpPlayer() +{ + mxAudioNode->getSource() >>= maSoundURL; + + OSL_ENSURE( !maSoundURL.isEmpty(), + "could not extract sound source URL/empty URL string" ); + + ENSURE_OR_THROW( getContext().mxComponentContext.is(), + "Invalid component context" ); +} + +void AnimationAudioNode::dispose() +{ + resetPlayer(); + mxAudioNode.clear(); + BaseNode::dispose(); +} + +void AnimationAudioNode::activate_st() +{ + createPlayer(); + + AnimationEventHandlerSharedPtr aHandler( + std::dynamic_pointer_cast<AnimationEventHandler>( getSelf() ) ); + OSL_ENSURE( aHandler, + "could not cast self to AnimationEventHandler?" ); + getContext().mrEventMultiplexer.addCommandStopAudioHandler( aHandler ); + + if (mpPlayer && mpPlayer->startPlayback()) + { + // TODO(F2): Handle end time attribute, too + if( getXAnimationNode()->getDuration().hasValue() ) + { + scheduleDeactivationEvent(); + } + else + { + // no node duration. Take inherent media time. We have to recheck + // if the player is playing in case the duration isn't accurate + // or the progress fall behind. + auto self(getSelf()); + scheduleDeactivationEvent( + makeDelay( [this] () { this->checkPlayingStatus(); }, + mpPlayer->getDuration(), + "AnimationAudioNode::check if still playing with delay") ); + } + } + else + { + // deactivate ASAP: + auto self(getSelf()); + scheduleDeactivationEvent( + makeEvent( [self] () { self->deactivate(); }, + "AnimationAudioNode::deactivate without delay") ); + } +} + +// TODO(F2): generate deactivation event, when sound +// is over + +namespace { + +// libc++ and MSVC std::bind doesn't cut it here, and it's not possible to use +// a lambda because the preprocessor thinks that comma in capture list +// separates macro parameters +struct NotifyAudioStopped +{ + EventMultiplexer & m_rEventMultiplexer; + ::std::shared_ptr<BaseNode> m_pSelf; + NotifyAudioStopped(EventMultiplexer & rEventMultiplexer, + ::std::shared_ptr<BaseNode> const& pSelf) + : m_rEventMultiplexer(rEventMultiplexer), m_pSelf(pSelf) { } + + void operator()() + { + m_rEventMultiplexer.notifyAudioStopped(m_pSelf); + } +}; + +} + +void AnimationAudioNode::deactivate_st( NodeState /*eDestState*/ ) +{ + AnimationEventHandlerSharedPtr aHandler( + std::dynamic_pointer_cast<AnimationEventHandler>( getSelf() ) ); + OSL_ENSURE( aHandler, + "could not cast self to AnimationEventHandler?" ); + getContext().mrEventMultiplexer.removeCommandStopAudioHandler( aHandler ); + + // force-end sound + if (mpPlayer) + { + mpPlayer->stopPlayback(); + resetPlayer(); + } + + // notify _after_ state change: + getContext().mrEventQueue.addEvent( + makeEvent( NotifyAudioStopped(getContext().mrEventMultiplexer, getSelf()), + "AnimationAudioNode::notifyAudioStopped") ); +} + +bool AnimationAudioNode::hasPendingAnimation() const +{ + // force slide to use the animation framework + // (otherwise, a single sound on the slide would + // not be played). + return true; +} + +void AnimationAudioNode::createPlayer() const +{ + if (mpPlayer) + return; + + try + { + mpPlayer = SoundPlayer::create( getContext().mrEventMultiplexer, + maSoundURL, + getContext().mxComponentContext, + getContext().mrMediaFileManager); + } + catch( lang::NoSupportException& ) + { + // catch possible exceptions from SoundPlayer, + // since being not able to playback the sound + // is not a hard error here (remainder of the + // animations should still work). + } +} + +void AnimationAudioNode::resetPlayer() const +{ + if (mpPlayer) + { + mpPlayer->stopPlayback(); + mpPlayer->dispose(); + mpPlayer.reset(); + } +} + +bool AnimationAudioNode::handleAnimationEvent( + const AnimationNodeSharedPtr& /*rNode*/ ) +{ + // TODO(F2): for now we support only STOPAUDIO events. + deactivate(); + return true; +} + +void AnimationAudioNode::checkPlayingStatus() +{ + auto self(getSelf()); + double nDuration = mpPlayer->getDuration(); + if (!mpPlayer->isPlaying() || nDuration < 0.0) + nDuration = 0.0; + + scheduleDeactivationEvent( + makeDelay( [self] () { self->deactivate(); }, + nDuration, + "AnimationAudioNode::deactivate with delay") ); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationaudionode.hxx b/slideshow/source/engine/animationnodes/animationaudionode.hxx new file mode 100644 index 000000000..dd308f04f --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationaudionode.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX + +#include <basecontainernode.hxx> +#include <soundplayer.hxx> +#include <com/sun/star/animations/XAnimationNode.hpp> +#include <com/sun/star/animations/XAudio.hpp> + +namespace slideshow::internal { + +/** Audio node. + + This animation node contains an audio effect. Duration and + start/stop behaviour is affected by the referenced audio + file. +*/ +class AnimationAudioNode : public BaseNode, public AnimationEventHandler +{ +public: + AnimationAudioNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + BaseContainerNodeSharedPtr const& pParent, + NodeContext const& rContext ); + +protected: + virtual void dispose() override; + +private: + virtual void activate_st() override; + virtual void deactivate_st( NodeState eDestState ) override; + virtual bool hasPendingAnimation() const override; + + /// overridden, because we need to deal with STOPAUDIO commands + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override; + +private: + css::uno::Reference<css::animations::XAudio > mxAudioNode; + OUString maSoundURL; + mutable SoundPlayerSharedPtr mpPlayer; + + void createPlayer() const; + void resetPlayer() const; + void checkPlayingStatus(); +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationbasenode.cxx b/slideshow/source/engine/animationnodes/animationbasenode.cxx new file mode 100644 index 000000000..1a15bf2de --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationbasenode.cxx @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sal/log.hxx> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <com/sun/star/animations/Timing.hpp> +#include <com/sun/star/animations/AnimationAdditiveMode.hpp> +#include <com/sun/star/presentation/ShapeAnimationSubType.hpp> + +#include "nodetools.hxx" +#include <doctreenode.hxx> +#include "animationbasenode.hxx" +#include <delayevent.hxx> +#include <framerate.hxx> + +#include <optional> +#include <algorithm> + +using namespace com::sun::star; + +namespace slideshow::internal { + +AnimationBaseNode::AnimationBaseNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : BaseNode( xNode, rParent, rContext ), + mxAnimateNode( xNode, uno::UNO_QUERY_THROW ), + maAttributeLayerHolder(), + maSlideSize( rContext.maSlideSize ), + mpShape(), + mpShapeSubset(), + mpSubsetManager(rContext.maContext.mpSubsettableShapeManager), + mbPreservedVisibility(true), + mbIsIndependentSubset( rContext.mbIsIndependentSubset ), + mpActivity() +{ + // extract native node targets + // =========================== + + // plain shape target + uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(), + uno::UNO_QUERY ); + + // distinguish 5 cases: + + // - plain shape target + // (NodeContext.mpMasterShapeSubset full set) + + // - parent-generated subset (generate an + // independent subset) + + // - parent-generated subset from iteration + // (generate a dependent subset) + + // - XShape target at the XAnimatioNode (generate + // a plain shape target) + + // - ParagraphTarget target at the XAnimationNode + // (generate an independent shape subset) + if( rContext.mpMasterShapeSubset ) + { + if( rContext.mpMasterShapeSubset->isFullSet() ) + { + // case 1: plain shape target from parent + mpShape = rContext.mpMasterShapeSubset->getSubsetShape(); + } + else + { + // cases 2 & 3: subset shape + mpShapeSubset = rContext.mpMasterShapeSubset; + } + } + else + { + // no parent-provided shape, try to extract + // from XAnimationNode - cases 4 and 5 + + if( xShape.is() ) + { + mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, + xShape ); + } + else + { + // no shape provided. Maybe a ParagraphTarget? + presentation::ParagraphTarget aTarget; + + if( !(mxAnimateNode->getTarget() >>= aTarget) ) + ENSURE_OR_THROW( + false, "could not extract any target information" ); + + xShape = aTarget.Shape; + + ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" ); + + mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, + xShape ); + + // NOTE: For shapes with ParagraphTarget, we ignore + // the SubItem property. We implicitly assume that it + // is set to ONLY_TEXT. + OSL_ENSURE( + mxAnimateNode->getSubItem() == + presentation::ShapeAnimationSubType::ONLY_TEXT || + mxAnimateNode->getSubItem() == + presentation::ShapeAnimationSubType::AS_WHOLE, + "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? " + "Make up your mind, I'll ignore the subitem." ); + + // okay, found a ParagraphTarget with a valid XShape. Does the shape + // provide the given paragraph? + if( aTarget.Paragraph >= 0 && + mpShape->getTreeNodeSupplier().getNumberOfTreeNodes( + DocTreeNode::NodeType::LogicalParagraph) > aTarget.Paragraph ) + { + const DocTreeNode& rTreeNode( + mpShape->getTreeNodeSupplier().getTreeNode( + aTarget.Paragraph, + DocTreeNode::NodeType::LogicalParagraph ) ); + + // CAUTION: the creation of the subset shape + // _must_ stay in the node constructor, since + // Slide::prefetchShow() initializes shape + // attributes right after animation import (or + // the Slide class must be changed). + mpShapeSubset = + std::make_shared<ShapeSubset>( mpShape, + rTreeNode, + mpSubsetManager ); + + // Override NodeContext, and flag this node as + // a special independent subset one. This is + // important when applying initial attributes: + // independent shape subsets must be setup + // when the slide starts, since they, as their + // name suggest, can have state independent to + // the master shape. The following example + // might illustrate that: a master shape has + // no effect, one of the text paragraphs + // within it has an appear effect. Now, the + // respective paragraph must be invisible when + // the slide is initially shown, and become + // visible only when the effect starts. + mbIsIndependentSubset = true; + + // already enable subset right here, the + // setup of initial shape attributes of + // course needs the subset shape + // generated, to apply e.g. visibility + // changes. + mpShapeSubset->enableSubsetShape(); + } + } + } +} + +void AnimationBaseNode::dispose() +{ + if (mpActivity) { + mpActivity->dispose(); + mpActivity.reset(); + } + + maAttributeLayerHolder.reset(); + mxAnimateNode.clear(); + mpShape.reset(); + mpShapeSubset.reset(); + + BaseNode::dispose(); +} + +bool AnimationBaseNode::init_st() +{ + // if we've still got an old activity lying around, dispose it: + if (mpActivity) { + mpActivity->dispose(); + mpActivity.reset(); + } + + // note: actually disposing the activity too early might cause problems, + // because on dequeued() it calls endAnimation(pAnim->end()), thus ending + // animation _after_ last screen update. + // review that end() is properly called (which calls endAnimation(), too). + + try { + // TODO(F2): For restart functionality, we must regenerate activities, + // since they are not able to reset their state (or implement _that_) + mpActivity = createActivity(); + } + catch (uno::Exception const&) { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + // catch and ignore. We later handle empty activities, but for + // other nodes to function properly, the core functionality of + // this node must remain up and running. + } + return true; +} + +bool AnimationBaseNode::resolve_st() +{ + // enable shape subset for automatically generated + // subsets. Independent subsets are already setup + // during construction time. Doing it only here + // saves us a lot of sprites and shapes lying + // around. This is especially important for + // character-wise iterations, since the shape + // content (e.g. thousands of characters) would + // otherwise be painted character-by-character. + if (isDependentSubsettedShape() && mpShapeSubset) { + mpShapeSubset->enableSubsetShape(); + } + return true; +} + +void AnimationBaseNode::activate_st() +{ + AttributableShapeSharedPtr const pShape(getShape()); + mbPreservedVisibility = pShape->isVisible(); + + // create new attribute layer + maAttributeLayerHolder.createAttributeLayer(pShape); + + ENSURE_OR_THROW( maAttributeLayerHolder.get(), + "Could not generate shape attribute layer" ); + + // TODO(Q2): This affects the way mpActivity + // works, but is performed here because of + // locality (we're fiddling with the additive mode + // here, anyway, and it's the only place where we + // do). OTOH, maybe the complete additive mode + // setup should be moved to the activities. + + // for simple by-animations, the SMIL spec + // requires us to emulate "0,by-value" value list + // behaviour, with additive mode forced to "sum", + // no matter what the input is + // (http://www.w3.org/TR/smil20/animation.html#adef-by). + if( mxAnimateNode->getBy().hasValue() && + !mxAnimateNode->getTo().hasValue() && + !mxAnimateNode->getFrom().hasValue() ) + { + // force attribute mode to REPLACE (note the + // subtle discrepancy to the paragraph above, + // where SMIL requires SUM. This is internally + // handled by the FromToByActivity, and is + // because otherwise DOM values would not be + // handled correctly: the activity cannot + // determine whether an + // Activity::getUnderlyingValue() yields the + // DOM value, or already a summed-up conglomerate) + + // Note that this poses problems with our + // hybrid activity duration (time or min number of frames), + // since if activities + // exceed their duration, wrong 'by' start + // values might arise ('Laser effect') + maAttributeLayerHolder.get()->setAdditiveMode( + animations::AnimationAdditiveMode::REPLACE ); + } + else + { + // apply additive mode to newly created Attribute layer + maAttributeLayerHolder.get()->setAdditiveMode( + mxAnimateNode->getAdditive() ); + } + + // fake normal animation behaviour, even if we + // show nothing. This is the appropriate way to + // handle errors on Activity generation, because + // maybe all other effects on the slide are + // correctly initialized (but won't run, if we + // signal an error here) + if (mpActivity) { + // supply Activity (and the underlying Animation) with + // it's AttributeLayer, to perform the animation on + mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() ); + + // add to activities queue + enqueueActivity(); + } + else { + // Actually, DO generate the event for empty activity, + // to keep the chain of animations running + BaseNode::scheduleDeactivationEvent(); + } +} + +void AnimationBaseNode::deactivate_st( NodeState eDestState ) +{ + if (eDestState == FROZEN && mpActivity) + mpActivity->end(); + + if (isDependentSubsettedShape()) { + // for dependent subsets, remove subset shape + // from layer, re-integrate subsetted part + // back into original shape. For independent + // subsets, we cannot make any assumptions + // about subset attribute state relative to + // master shape, thus, have to keep it. This + // will effectively re-integrate the subsetted + // part into the original shape (whose + // animation will hopefully have ended, too) + + // this statement will save a whole lot of + // sprites for iterated text effects, since + // those sprites will only exist during the + // actual lifetime of the effects + if (mpShapeSubset) { + mpShapeSubset->disableSubsetShape(); + } + } + + if (eDestState != ENDED) + return; + + // no shape anymore, no layer needed: + maAttributeLayerHolder.reset(); + + if (! isDependentSubsettedShape()) { + + // for all other shapes, removing the + // attribute layer quite possibly changes + // shape display. Thus, force update + AttributableShapeSharedPtr const pShape( getShape() ); + + // don't anybody dare to check against + // pShape->isVisible() here, removing the + // attribute layer might actually make the + // shape invisible! + getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape ); + } + + if (mpActivity) { + // kill activity, if still running + mpActivity->dispose(); + mpActivity.reset(); + } +} + +void AnimationBaseNode::removeEffect() +{ + if (!isDependentSubsettedShape()) { + AttributableShapeSharedPtr const pShape(getShape()); + pShape->setVisibility(!mbPreservedVisibility); + getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape ); + pShape->setVisibility(mbPreservedVisibility); + } +} + +bool AnimationBaseNode::hasPendingAnimation() const +{ + // TODO(F1): This might not always be true. Are there 'inactive' + // animation nodes? + return true; +} + +bool AnimationBaseNode::enqueueActivity() const +{ + return getContext().mrActivitiesQueue.addActivity( mpActivity ); +} + +#if defined(DBG_UTIL) +void AnimationBaseNode::showState() const +{ + BaseNode::showState(); + + SAL_INFO( "slideshow.verbose", "AnimationBaseNode info: independent subset=" << + (mbIsIndependentSubset ? "y" : "n") ); +} +#endif + +ActivitiesFactory::CommonParameters +AnimationBaseNode::fillCommonParameters() const +{ + double nDuration = 0.0; + + // TODO(F3): Duration/End handling is barely there + if( !(mxAnimateNode->getDuration() >>= nDuration) ) { + mxAnimateNode->getEnd() >>= nDuration; // Wah. + } + + // minimal duration we fallback to (avoid 0 here!) + nDuration = ::std::max( 0.001, nDuration ); + + const bool bAutoReverse( mxAnimateNode->getAutoReverse() ); + + std::optional<double> aRepeats; + double nRepeats = 0; + if( mxAnimateNode->getRepeatCount() >>= nRepeats ) { + aRepeats = nRepeats; + } + else { + if( mxAnimateNode->getRepeatDuration() >>= nRepeats ) { + // when repeatDuration is given, + // autoreverse does _not_ modify the + // active duration. Thus, calc repeat + // count with already adapted simple + // duration (twice the specified duration) + + // convert duration back to repeat counts + if( bAutoReverse ) + aRepeats = nRepeats / (2.0 * nDuration); + else + aRepeats = nRepeats / nDuration; + } + else + { + // no double value for both values - Timing::INDEFINITE? + animations::Timing eTiming; + + if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) || + eTiming != animations::Timing_INDEFINITE ) + { + if( !(mxAnimateNode->getRepeatCount() >>= eTiming) || + eTiming != animations::Timing_INDEFINITE ) + { + // no indefinite timing, no other values given - + // use simple run, i.e. repeat of 1.0 + aRepeats = 1.0; + } + } + } + } + + // calc accel/decel: + double nAcceleration = 0.0; + double nDeceleration = 0.0; + BaseNodeSharedPtr const pSelf( getSelf() ); + for ( std::shared_ptr<BaseNode> pNode( pSelf ); + pNode; pNode = pNode->getParentNode() ) + { + uno::Reference<animations::XAnimationNode> const xAnimationNode( + pNode->getXAnimationNode() ); + nAcceleration = std::max( nAcceleration, + xAnimationNode->getAcceleration() ); + nDeceleration = std::max( nDeceleration, + xAnimationNode->getDecelerate() ); + } + + EventSharedPtr pEndEvent; + if (pSelf) { + pEndEvent = makeEvent( [pSelf] () {pSelf->deactivate(); }, + "AnimationBaseNode::deactivate"); + } + + // Calculate the minimum frame count that depends on the duration and + // the minimum frame count. + const sal_Int32 nMinFrameCount (std::clamp<sal_Int32>( + basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10)); + + return ActivitiesFactory::CommonParameters( + pEndEvent, + getContext().mrEventQueue, + getContext().mrActivitiesQueue, + nDuration, + nMinFrameCount, + bAutoReverse, + aRepeats, + nAcceleration, + nDeceleration, + getShape(), + getSlideSize()); +} + +AttributableShapeSharedPtr const & AnimationBaseNode::getShape() const +{ + // any subsetting at all? + if (mpShapeSubset) + return mpShapeSubset->getSubsetShape(); + else + return mpShape; // nope, plain shape always +} + +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationbasenode.hxx b/slideshow/source/engine/animationnodes/animationbasenode.hxx new file mode 100644 index 000000000..6bb5cd1f2 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationbasenode.hxx @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX + +#include <com/sun/star/animations/XAnimate.hpp> + +#include <basecontainernode.hxx> +#include <activitiesfactory.hxx> +#include <shapeattributelayerholder.hxx> +#include <attributableshape.hxx> +#include <shapesubset.hxx> + +namespace slideshow::internal { + +/** Common base class for all leaf animation nodes. + + This class basically holds the target shape +*/ +class AnimationBaseNode : public BaseNode +{ +public: + AnimationBaseNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + BaseContainerNodeSharedPtr const& pParent, + NodeContext const& rContext ); + +#if defined(DBG_UTIL) + virtual void showState() const override; +#endif + virtual void removeEffect() override; + +protected: + virtual void dispose() override; + + css::uno::Reference<css::animations::XAnimate> const& getXAnimateNode() const + { return mxAnimateNode; } + + /// Create parameter struct for ActivitiesFactory + ActivitiesFactory::CommonParameters fillCommonParameters() const; + ::basegfx::B2DVector const& getSlideSize() const { return maSlideSize; } + AttributableShapeSharedPtr const & getShape() const; + +private: + virtual bool hasPendingAnimation() const override; + virtual bool enqueueActivity() const; + +private: // state transition callbacks + virtual bool init_st() override; + virtual bool resolve_st() override; + virtual void activate_st() override; + virtual void deactivate_st( NodeState eDestState ) override; + virtual AnimationActivitySharedPtr createActivity() const = 0; + +private: + /** Returns true, if this is a subset animation, and + the subset is autogenerated (e.g. from an + iteration) + */ + bool isDependentSubsettedShape() const + { return mpShapeSubset && !mbIsIndependentSubset; } + +private: + css::uno::Reference<css::animations::XAnimate> mxAnimateNode; + ShapeAttributeLayerHolder maAttributeLayerHolder; + ::basegfx::B2DVector maSlideSize; + + /// When valid, this node has a plain target shape + AttributableShapeSharedPtr mpShape; + /// When valid, this is a subsetted target shape + ShapeSubsetSharedPtr mpShapeSubset; + SubsettableShapeManagerSharedPtr mpSubsetManager; + bool mbPreservedVisibility; + bool mbIsIndependentSubset; + +protected: + AnimationActivitySharedPtr mpActivity; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationcolornode.cxx b/slideshow/source/engine/animationnodes/animationcolornode.cxx new file mode 100644 index 000000000..14a32aae4 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationcolornode.cxx @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/animations/AnimationColorSpace.hpp> + +#include <coloranimation.hxx> +#include <hslcoloranimation.hxx> +#include "animationcolornode.hxx" +#include <animationfactory.hxx> +#include <activitiesfactory.hxx> + +using namespace com::sun::star; + +namespace slideshow::internal { + +namespace { +/** Little wrapper for HSL to RGB mapping. + + This class implements the HSLColorAnimation interface, + internally converting to RGB and forwarding to + ColorAnimation. +*/ +class HSLWrapper : public HSLColorAnimation +{ +public: + explicit HSLWrapper( const ColorAnimationSharedPtr& rAnimation ) + : mpAnimation( rAnimation ) + { + ENSURE_OR_THROW( + mpAnimation, + "HSLWrapper::HSLWrapper(): Invalid color animation delegate" ); + } + + virtual void prefetch() override + {} + + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override + { + mpAnimation->start( rShape, rAttrLayer ); + } + + virtual void end() override + { + mpAnimation->end(); + } + + virtual bool operator()( const HSLColor& rColor ) override + { + return (*mpAnimation)( RGBColor( rColor ) ); + } + + virtual HSLColor getUnderlyingValue() const override + { + return HSLColor( mpAnimation->getUnderlyingValue() ); + } + +private: + ColorAnimationSharedPtr mpAnimation; +}; + +} // anon namespace + +AnimationActivitySharedPtr AnimationColorNode::createActivity() const +{ + ActivitiesFactory::CommonParameters aParms( fillCommonParameters() ); + + switch( mxColorNode->getColorInterpolation() ) + { + case animations::AnimationColorSpace::RGB: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createColorPropertyAnimation( + mxColorNode->getAttributeName(), + getShape(), + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + getXAnimateNode() ); + + case animations::AnimationColorSpace::HSL: + // Wrap a plain ColorAnimation with the HSL + // wrapper, which implements the HSLColorAnimation + // interface, and internally converts HSL to RGB color + return ActivitiesFactory::createAnimateActivity( + aParms, + std::make_shared<HSLWrapper>( + AnimationFactory::createColorPropertyAnimation( + mxColorNode->getAttributeName(), + getShape(), + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld )), + mxColorNode ); + + default: + ENSURE_OR_THROW( false, "AnimationColorNode::createColorActivity(): " + "Unexpected color space" ); + } + + return AnimationActivitySharedPtr(); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationcolornode.hxx b/slideshow/source/engine/animationnodes/animationcolornode.hxx new file mode 100644 index 000000000..8d5d42898 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationcolornode.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX + +#include "animationbasenode.hxx" +#include <com/sun/star/animations/XAnimateColor.hpp> + +namespace slideshow::internal { + +class AnimationColorNode : public AnimationBaseNode +{ +public: + AnimationColorNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) + : AnimationBaseNode( xNode, pParent, rContext ), + mxColorNode( xNode, css::uno::UNO_QUERY_THROW ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override { return "AnimationColorNode"; } +#endif + +private: + virtual AnimationActivitySharedPtr createActivity() const override; + + css::uno::Reference<css::animations::XAnimateColor > mxColorNode; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.cxx b/slideshow/source/engine/animationnodes/animationcommandnode.cxx new file mode 100644 index 000000000..df70cb1ab --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationcommandnode.cxx @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/presentation/EffectCommands.hpp> +#include <com/sun/star/presentation/EffectNodeType.hpp> +#include <com/sun/star/animations/AnimationNodeType.hpp> +#include <com/sun/star/animations/XAudio.hpp> +#include <com/sun/star/animations/Timing.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/sequenceashashmap.hxx> + +#include "animationcommandnode.hxx" +#include <eventmultiplexer.hxx> +#include <delayevent.hxx> + + +using namespace com::sun::star; + +namespace +{ +/// Determines if this is the root of the timing node tree. +bool IsTimingRootNode(const uno::Reference<animations::XAnimationNode>& xNode) +{ + uno::Sequence<beans::NamedValue> aUserData = xNode->getUserData(); + comphelper::SequenceAsHashMap aMap(aUserData); + auto it = aMap.find("node-type"); + if (it == aMap.end()) + { + return false; + } + + sal_Int16 nNodeType{}; + if (!(it->second >>= nNodeType)) + { + return false; + } + + return nNodeType == css::presentation::EffectNodeType::TIMING_ROOT; +} + +/// Walks the parent chain of xNode and stops at the timing root. +uno::Reference<animations::XAnimationNode> +GetTimingRoot(const uno::Reference<animations::XAnimationNode>& xNode) +{ + uno::Reference<animations::XAnimationNode> xParent(xNode->getParent(), uno::UNO_QUERY); + while (true) + { + if (!xParent.is()) + { + break; + } + + if (IsTimingRootNode(xParent)) + { + return xParent; + } + + xParent.set(xParent->getParent(), uno::UNO_QUERY); + } + + return {}; +} +} + +namespace slideshow::internal { + +namespace EffectCommands = css::presentation::EffectCommands; + +AnimationCommandNode::AnimationCommandNode( uno::Reference<animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) : + BaseNode( xNode, pParent, rContext ), + mpShape(), + mxCommandNode( xNode, css::uno::UNO_QUERY_THROW ) +{ + uno::Reference< drawing::XShape > xShape( mxCommandNode->getTarget(), + uno::UNO_QUERY ); + ShapeSharedPtr pShape( getContext().mpSubsettableShapeManager->lookupShape( xShape ) ); + mpShape = ::std::dynamic_pointer_cast< IExternalMediaShapeBase >( pShape ); + mxShape = xShape; +} + +void AnimationCommandNode::dispose() +{ + mxCommandNode.clear(); + mpShape.reset(); + BaseNode::dispose(); +} + +bool AnimationCommandNode::GetLoopingFromAnimation( + const uno::Reference<animations::XCommand>& xCommandNode, + const uno::Reference<drawing::XShape>& xShape) +{ + uno::Reference<animations::XAnimationNode> xTimingRoot = GetTimingRoot(xCommandNode); + uno::Reference<container::XEnumerationAccess> xEnumAccess(xTimingRoot, uno::UNO_QUERY); + if (!xEnumAccess.is()) + { + return false; + } + + uno::Reference<container::XEnumeration> xNodes = xEnumAccess->createEnumeration(); + while (xNodes->hasMoreElements()) + { + uno::Reference<animations::XAnimationNode> xNode(xNodes->nextElement(), uno::UNO_QUERY); + if (xNode->getType() != animations::AnimationNodeType::AUDIO) + { + continue; + } + + uno::Reference<animations::XAudio> xAudio(xNode, uno::UNO_QUERY); + uno::Reference<drawing::XShape> xSource(xAudio->getSource(), uno::UNO_QUERY); + if (xSource != xShape) + { + continue; + } + + animations::Timing eTiming{}; + if ((xAudio->getRepeatCount() >>= eTiming) && eTiming == animations::Timing_INDEFINITE) + { + return true; + } + } + return false; +} + +void AnimationCommandNode::activate_st() +{ + switch( mxCommandNode->getCommand() ) { + // the command is user defined + case EffectCommands::CUSTOM: break; + // the command is an ole verb. + case EffectCommands::VERB: break; + // the command starts playing on a media object + case EffectCommands::PLAY: + { + double fMediaTime=0.0; + beans::PropertyValue aMediaTime; + if( (mxCommandNode->getParameter() >>= aMediaTime) && aMediaTime.Name == "MediaTime" ) + { + aMediaTime.Value >>= fMediaTime; + } + if( mpShape ) + { + mpShape->setMediaTime(fMediaTime/1000.0); + + if (AnimationCommandNode::GetLoopingFromAnimation(mxCommandNode, mxShape)) + { + // If looping is requested from the animation, then that has priority over the + // looping from the shape itself. + mpShape->setLooping(true); + } + + mpShape->play(); + } + break; + } + // the command toggles the pause status on a media object + case EffectCommands::TOGGLEPAUSE: + { + if (mpShape) + { + if( mpShape->isPlaying() ) + mpShape->pause(); + else + mpShape->play(); + } + break; + } + // the command stops the animation on a media object + case EffectCommands::STOP: + { + if( mpShape ) + mpShape->stop(); + break; + } + // the command stops all currently running sound effects + case EffectCommands::STOPAUDIO: + getContext().mrEventMultiplexer.notifyCommandStopAudio( getSelf() ); + break; + } + + // deactivate ASAP: + auto self(getSelf()); + scheduleDeactivationEvent( + makeEvent( [self] () { self->deactivate(); }, + "AnimationCommandNode::deactivate" ) ); +} + +bool AnimationCommandNode::hasPendingAnimation() const +{ + return mxCommandNode->getCommand() == EffectCommands::STOPAUDIO || mpShape; +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.hxx b/slideshow/source/engine/animationnodes/animationcommandnode.hxx new file mode 100644 index 000000000..96ca886f8 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationcommandnode.hxx @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX + +#include <slideshowdllapi.h> +#include <basecontainernode.hxx> +#include <iexternalmediashapebase.hxx> +#include <com/sun/star/animations/XCommand.hpp> + +namespace slideshow::internal { + +/** Command node. + + This animation node encapsulates a command. Not yet implemented: + verb & custom. +*/ +class SLIDESHOW_DLLPUBLIC AnimationCommandNode : public BaseNode +{ +public: + AnimationCommandNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ); + + /// Assuming that xCommandNode is a play command, determines if an audio node wants looping when + /// xShape plays. + static bool + GetLoopingFromAnimation(const css::uno::Reference<css::animations::XCommand>& xCommandNode, + const css::uno::Reference<css::drawing::XShape>& xShape); + +protected: + virtual void dispose() override; + +private: + virtual void activate_st() override; + virtual bool hasPendingAnimation() const override; + +private: + IExternalMediaShapeBaseSharedPtr mpShape; + css::uno::Reference<css::animations::XCommand > mxCommandNode; + css::uno::Reference<css::drawing::XShape> mxShape; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationnodefactory.cxx b/slideshow/source/engine/animationnodes/animationnodefactory.cxx new file mode 100644 index 000000000..4df097cb0 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationnodefactory.cxx @@ -0,0 +1,598 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/animations/AnimationNodeType.hpp> +#include <com/sun/star/presentation/TextAnimationType.hpp> +#include <com/sun/star/animations/XIterateContainer.hpp> +#include <com/sun/star/presentation/ShapeAnimationSubType.hpp> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <basegfx/numeric/ftools.hxx> +#include <sal/log.hxx> + +#include <animationnodefactory.hxx> +#include "paralleltimecontainer.hxx" +#include "sequentialtimecontainer.hxx" +#include "propertyanimationnode.hxx" +#include "animationsetnode.hxx" +#include "animationpathmotionnode.hxx" +#include "animationphysicsnode.hxx" +#include "animationcolornode.hxx" +#include "animationtransformnode.hxx" +#include "animationtransitionfilternode.hxx" +#include "animationaudionode.hxx" +#include "animationcommandnode.hxx" +#include "nodetools.hxx" +#include <tools.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +namespace slideshow::internal { + +namespace { + +// forward declaration needed by NodeCreator +BaseNodeSharedPtr implCreateAnimationNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ); + +class NodeCreator +{ +public: + NodeCreator( BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : mrParent( rParent ), mrContext( rContext ) {} + + void operator()( + const uno::Reference< animations::XAnimationNode >& xChildNode ) const + { + createChild( xChildNode, mrContext ); + } + +protected: + void createChild( + const uno::Reference< animations::XAnimationNode >& xChildNode, + const NodeContext& rContext ) const + { + BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode, + mrParent, + rContext ) ); + + OSL_ENSURE( pChild, + "NodeCreator::operator(): child creation failed" ); + + // TODO(Q1): This yields circular references, which, it seems, is + // unavoidable here + if( pChild ) + mrParent->appendChildNode( pChild ); + } + + BaseContainerNodeSharedPtr& mrParent; + const NodeContext& mrContext; +}; + +/** Same as NodeCreator, only that NodeContext's + SubsetShape is cloned for every child node. + + This is used for iterated animation node generation +*/ +class CloningNodeCreator : private NodeCreator +{ +public: + CloningNodeCreator( BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : NodeCreator( rParent, rContext ) {} + + void operator()( + const uno::Reference< animations::XAnimationNode >& xChildNode ) const + { + NodeContext aContext( mrContext ); + + // TODO(Q1): There's a catch here. If you clone a + // subset whose actual subsetting has already been + // realized (i.e. if enableSubsetShape() has been + // called already), and the original of your clone + // goes out of scope, then your subset will be + // gone (SubsettableShapeManager::revokeSubset() be + // called). As of now, this behaviour is not + // triggered here (we either clone, XOR we enable + // subset initially), but one might consider + // reworking DrawShape/ShapeSubset to avoid this. + + // clone ShapeSubset, since each node needs their + // own version of the ShapeSubset (otherwise, + // e.g. activity counting does not work - subset + // would be removed after first animation node + // disables it). + + // NOTE: this is only a problem for animation + // nodes that explicitly call + // disableSubsetShape(). Independent shape subsets + // (like those created for ParagraphTargets) + // solely rely on the ShapeSubset destructor to + // normalize things, which does the right thing + // here: the subset is only removed after _the + // last_ animation node releases the shared ptr. + aContext.mpMasterShapeSubset = + std::make_shared<ShapeSubset>( *aContext.mpMasterShapeSubset ); + + createChild( xChildNode, aContext ); + } +}; + +/** Create animation nodes for text iterations + + This method clones the animation nodes below xIterNode + for every iterated shape entity. +*/ +bool implCreateIteratedNodes( + const uno::Reference< animations::XIterateContainer >& xIterNode, + BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) +{ + ENSURE_OR_THROW( xIterNode.is(), + "implCreateIteratedNodes(): Invalid node" ); + + const double nIntervalTimeout( xIterNode->getIterateInterval() ); + + // valid iterate interval? We're ruling out monstrous + // values here, to avoid pseudo 'hangs' in the + // presentation + if( nIntervalTimeout < 0.0 || + nIntervalTimeout > 1000.0 ) + { + return false; // not an active iteration + } + + if( ::basegfx::fTools::equalZero( nIntervalTimeout ) ) + SAL_INFO("slideshow", "implCreateIteratedNodes(): " + "iterate interval close to zero, there's " + "no point in defining such an effect " + "(visually equivalent to whole-shape effect)" ); + + // Determine target shape (or subset) + // ================================== + + // TODO(E1): I'm not too sure what to expect here... + ENSURE_OR_RETURN_FALSE( + xIterNode->getTarget().hasValue(), + "implCreateIteratedNodes(): no target on ITERATE node" ); + + uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(), + uno::UNO_QUERY ); + + presentation::ParagraphTarget aTarget; + sal_Int16 nSubItem( xIterNode->getSubItem() ); + bool bParagraphTarget( false ); + + if( !xTargetShape.is() ) + { + // no shape provided. Maybe a ParagraphTarget? + if( !(xIterNode->getTarget() >>= aTarget) ) + ENSURE_OR_RETURN_FALSE( + false, + "implCreateIteratedNodes(): could not extract any " + "target information" ); + + xTargetShape = aTarget.Shape; + + ENSURE_OR_RETURN_FALSE( + xTargetShape.is(), + "implCreateIteratedNodes(): invalid shape in ParagraphTarget" ); + + // we've a paragraph target to iterate over, thus, + // the whole animation container refers only to + // the text + nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT; + + bParagraphTarget = true; + } + + // Lookup shape, and fill NodeContext + // ================================== + + AttributableShapeSharedPtr pTargetShape( + lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager, + xTargetShape ) ); + + const DocTreeNodeSupplier& rTreeNodeSupplier( + pTargetShape->getTreeNodeSupplier() ); + + ShapeSubsetSharedPtr pTargetSubset; + + NodeContext aContext( rContext ); + + // paragraph targets already need a subset as the + // master shape (they're representing only a single + // paragraph) + if( bParagraphTarget ) + { + ENSURE_OR_RETURN_FALSE( + aTarget.Paragraph >= 0 && + rTreeNodeSupplier.getNumberOfTreeNodes( + DocTreeNode::NodeType::LogicalParagraph ) > aTarget.Paragraph, + "implCreateIteratedNodes(): paragraph index out of range" ); + + pTargetSubset = + std::make_shared<ShapeSubset>( + pTargetShape, + // retrieve index aTarget.Paragraph of + // type PARAGRAPH from this shape + rTreeNodeSupplier.getTreeNode( + aTarget.Paragraph, + DocTreeNode::NodeType::LogicalParagraph ), + rContext.maContext.mpSubsettableShapeManager ); + + // iterate target is not the whole shape, but only + // the selected paragraph - subset _must_ be + // independent, to be able to affect visibility + // independent of master shape + aContext.mbIsIndependentSubset = true; + + // already enable parent subset right here, to + // make potentially generated subsets subtract + // their content from the parent subset (and not + // the original shape). Otherwise, already + // subsetted parents (e.g. paragraphs) would not + // have their characters removed, when the child + // iterations start. + // Furthermore, the setup of initial shape + // attributes of course needs the subset shape + // generated, to apply e.g. visibility changes. + pTargetSubset->enableSubsetShape(); + } + else + { + pTargetSubset = + std::make_shared<ShapeSubset>( pTargetShape, + rContext.maContext.mpSubsettableShapeManager ); + } + + aContext.mpMasterShapeSubset = pTargetSubset; + uno::Reference< animations::XAnimationNode > xNode( xIterNode, + uno::UNO_QUERY_THROW ); + + // Generate subsets + // ================ + + if( bParagraphTarget || + nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT ) + { + // prepend with animations for + // full Shape (will be subtracted + // from the subset parts within + // the Shape::createSubset() + // method). For ONLY_TEXT effects, + // we skip this part, to animate + // only the text. + + // OR + + // prepend with subset animation for full + // _paragraph_, from which the individual + // paragraph subsets are subtracted. Note that the + // subitem is superfluous here, we always assume + // ONLY_TEXT, if a paragraph is referenced as the + // master of an iteration effect. + NodeCreator aCreator( rParent, aContext ); + if( !for_each_childNode( xNode, aCreator ) ) + { + ENSURE_OR_RETURN_FALSE( + false, + "implCreateIteratedNodes(): iterated child node creation failed" ); + } + } + + // TODO(F2): This does not do the correct + // thing. Having nSubItem be set to ONLY_BACKGROUND + // should result in the text staying unanimated in the + // foreground, while the shape moves in the background + // (this behaviour is perfectly possible with the + // slideshow engine, only that the text won't be + // currently visible, because animations are always in + // the foreground) + if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND ) + { + // determine type of subitem iteration (logical + // text unit to animate) + DocTreeNode::NodeType eIterateNodeType( + DocTreeNode::NodeType::LogicalCharacterCell ); + + switch( xIterNode->getIterateType() ) + { + case presentation::TextAnimationType::BY_PARAGRAPH: + eIterateNodeType = DocTreeNode::NodeType::LogicalParagraph; + break; + + case presentation::TextAnimationType::BY_WORD: + eIterateNodeType = DocTreeNode::NodeType::LogicalWord; + break; + + case presentation::TextAnimationType::BY_LETTER: + eIterateNodeType = DocTreeNode::NodeType::LogicalCharacterCell; + break; + + default: + ENSURE_OR_THROW( + false, "implCreateIteratedNodes(): " + "Unexpected IterateType on XIterateContainer"); + break; + } + + if( bParagraphTarget && + eIterateNodeType != DocTreeNode::NodeType::LogicalWord && + eIterateNodeType != DocTreeNode::NodeType::LogicalCharacterCell ) + { + // will not animate the whole paragraph, when + // only the paragraph is animated at all. + OSL_FAIL( "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" ); + } + else + { + // setup iteration parameters + + + // iterate target is the whole shape (or the + // whole parent subshape), thus, can save + // loads of subset shapes by generating them + // only when the effects become active - + // before and after the effect active + // duration, all attributes are shared by + // master shape and subset (since the iterated + // effects are all the same). + aContext.mbIsIndependentSubset = false; + + // determine number of nodes for given subitem + // type + sal_Int32 nTreeNodes( 0 ); + if( bParagraphTarget ) + { + // create the iterated subset _relative_ to + // the given paragraph index (i.e. animate the + // given subset type, but only when it's part + // of the given paragraph) + nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes( + pTargetSubset->getSubset(), + eIterateNodeType ); + } + else + { + // generate normal subset + nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes( + eIterateNodeType ); + } + + + // iterate node, generate copies of the children for each subset + + + // NodeContext::mnStartDelay contains additional node delay. + // This will make the duplicated nodes for each iteration start + // increasingly later. + aContext.mnStartDelay = nIntervalTimeout; + + for( sal_Int32 i=0; i<nTreeNodes; ++i ) + { + // create subset with the corresponding tree nodes + if( bParagraphTarget ) + { + // create subsets relative to paragraph subset + aContext.mpMasterShapeSubset = + std::make_shared<ShapeSubset>( + pTargetSubset, + rTreeNodeSupplier.getSubsetTreeNode( + pTargetSubset->getSubset(), + i, + eIterateNodeType ) ); + } + else + { + // create subsets from main shape + aContext.mpMasterShapeSubset = + std::make_shared<ShapeSubset>( pTargetSubset, + rTreeNodeSupplier.getTreeNode( + i, + eIterateNodeType ) ); + } + + CloningNodeCreator aCreator( rParent, aContext ); + if( !for_each_childNode( xNode, aCreator ) ) + { + ENSURE_OR_RETURN_FALSE( + false, "implCreateIteratedNodes(): " + "iterated child node creation failed" ); + } + + aContext.mnStartDelay += nIntervalTimeout; + } + } + } + + // done with iterate child generation + return true; +} + +BaseNodeSharedPtr implCreateAnimationNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) +{ + ENSURE_OR_THROW( xNode.is(), + "implCreateAnimationNode(): invalid XAnimationNode" ); + + BaseNodeSharedPtr pCreatedNode; + BaseContainerNodeSharedPtr pCreatedContainer; + + // create the internal node, corresponding to xNode + switch( xNode->getType() ) + { + case animations::AnimationNodeType::CUSTOM: + OSL_FAIL( "implCreateAnimationNode(): " + "CUSTOM not yet implemented" ); + return pCreatedNode; + + case animations::AnimationNodeType::PAR: + pCreatedNode = pCreatedContainer = + std::make_shared<ParallelTimeContainer>( xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ITERATE: + // map iterate container to ParallelTimeContainer. + // the iterating functionality is to be found + // below, (see method implCreateIteratedNodes) + pCreatedNode = pCreatedContainer = + std::make_shared<ParallelTimeContainer>( xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::SEQ: + pCreatedNode = pCreatedContainer = + std::make_shared<SequentialTimeContainer>( xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ANIMATE: + pCreatedNode = std::make_shared<PropertyAnimationNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::SET: + pCreatedNode = std::make_shared<AnimationSetNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ANIMATEMOTION: + pCreatedNode = std::make_shared<AnimationPathMotionNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ANIMATECOLOR: + pCreatedNode = std::make_shared<AnimationColorNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ANIMATETRANSFORM: + pCreatedNode = std::make_shared<AnimationTransformNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::ANIMATEPHYSICS: + pCreatedNode = std::make_shared<AnimationPhysicsNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::TRANSITIONFILTER: + pCreatedNode = std::make_shared<AnimationTransitionFilterNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::AUDIO: + pCreatedNode = std::make_shared<AnimationAudioNode>( + xNode, rParent, rContext ); + break; + + case animations::AnimationNodeType::COMMAND: + pCreatedNode = std::make_shared<AnimationCommandNode>( + xNode, rParent, rContext ); + break; + + default: + OSL_FAIL( "implCreateAnimationNode(): " + "invalid AnimationNodeType" ); + return pCreatedNode; + } + + // TODO(Q1): This yields circular references, which, it seems, is + // unavoidable here + + // HACK: node objects need shared_ptr to themselves, + // which we pass them here. + pCreatedNode->setSelf( pCreatedNode ); + + // if we've got a container node object, recursively add + // its children + if( pCreatedContainer ) + { + uno::Reference< animations::XIterateContainer > xIterNode( + xNode, uno::UNO_QUERY ); + + // when this node is an XIterateContainer with + // active iterations, this method will generate + // the appropriate children + if( xIterNode.is() ) + { + // note that implCreateIteratedNodes() might + // choose not to generate any child nodes + // (e.g. when the iterate timeout is outside + // sensible limits). Then, no child nodes are + // generated at all, since typically, child + // node attribute are incomplete for iteration + // children. + implCreateIteratedNodes( xIterNode, + pCreatedContainer, + rContext ); + } + else + { + // no iterate subset node, just plain child generation now + NodeCreator aCreator( pCreatedContainer, rContext ); + if( !for_each_childNode( xNode, aCreator ) ) + { + OSL_FAIL( "implCreateAnimationNode(): " + "child node creation failed" ); + return BaseNodeSharedPtr(); + } + } + } + + return pCreatedNode; +} + +} // anon namespace + +AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const ::basegfx::B2DVector& rSlideSize, + const SlideShowContext& rContext ) +{ + ENSURE_OR_THROW( + xNode.is(), + "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" ); + + return implCreateAnimationNode( + xNode, + BaseContainerNodeSharedPtr(), // no parent + NodeContext( rContext, + rSlideSize )); +} + +#if defined(DBG_UTIL) +void AnimationNodeFactory::showTree( AnimationNodeSharedPtr const & pRootNode ) +{ + if( pRootNode ) + DEBUG_NODES_SHOWTREE( std::dynamic_pointer_cast<BaseContainerNode>( + pRootNode).get() ); +} +#endif + +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx b/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx new file mode 100644 index 000000000..cbef1f3ea --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "animationpathmotionnode.hxx" +#include <animationfactory.hxx> + +namespace slideshow::internal { + +void AnimationPathMotionNode::dispose() +{ + mxPathMotionNode.clear(); + AnimationBaseNode::dispose(); +} + +AnimationActivitySharedPtr AnimationPathMotionNode::createActivity() const +{ + OUString aString; + ENSURE_OR_THROW( (mxPathMotionNode->getPath() >>= aString), + "no string-based SVG:d path found" ); + + ActivitiesFactory::CommonParameters const aParms( fillCommonParameters() ); + return ActivitiesFactory::createSimpleActivity( + aParms, + AnimationFactory::createPathMotionAnimation( + aString, + mxPathMotionNode->getAdditive(), + getShape(), + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, 0 ), + true ); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx b/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx new file mode 100644 index 000000000..8bd91e158 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX + +#include "animationbasenode.hxx" +#include <com/sun/star/animations/XAnimateMotion.hpp> + +namespace slideshow::internal { + +class AnimationPathMotionNode : public AnimationBaseNode +{ +public: + AnimationPathMotionNode( + const css::uno::Reference<css::animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : AnimationBaseNode( xNode, rParent, rContext ), + mxPathMotionNode( xNode, css::uno::UNO_QUERY_THROW ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override + { return "AnimationPathMotionNode"; } +#endif + +protected: + virtual void dispose() override; + +private: + virtual AnimationActivitySharedPtr createActivity() const override; + + css::uno::Reference<css::animations::XAnimateMotion > mxPathMotionNode; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationphysicsnode.cxx b/slideshow/source/engine/animationnodes/animationphysicsnode.cxx new file mode 100644 index 000000000..0502f35c1 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationphysicsnode.cxx @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "animationphysicsnode.hxx" +#include <animationfactory.hxx> + +constexpr double fDefaultStartVelocityX(0.0); +constexpr double fDefaultStartVelocityY(0.0); +constexpr double fDefaultDensity(1.0); +constexpr double fDefaultBounciness(0.1); + +namespace slideshow::internal +{ +void AnimationPhysicsNode::dispose() +{ + mxPhysicsMotionNode.clear(); + AnimationBaseNode::dispose(); +} + +AnimationActivitySharedPtr AnimationPhysicsNode::createActivity() const +{ + double fDuration(0.0); + ENSURE_OR_THROW((mxPhysicsMotionNode->getDuration() >>= fDuration), + "Couldn't get the animation duration."); + + ::css::uno::Any aTemp; + double fStartVelocityX = fDefaultStartVelocityX; + aTemp = mxPhysicsMotionNode->getStartVelocityX(); + if (aTemp.hasValue()) + aTemp >>= fStartVelocityX; + + double fStartVelocityY = fDefaultStartVelocityY; + aTemp = mxPhysicsMotionNode->getStartVelocityY(); + if (aTemp.hasValue()) + aTemp >>= fStartVelocityY; + + double fDensity = fDefaultDensity; + aTemp = mxPhysicsMotionNode->getDensity(); + if (aTemp.hasValue()) + { + aTemp >>= fDensity; + fDensity = (fDensity < 0.0) ? 0.0 : fDensity; + } + + double fBounciness = fDefaultBounciness; + aTemp = mxPhysicsMotionNode->getBounciness(); + if (aTemp.hasValue()) + { + aTemp >>= fBounciness; + fBounciness = std::clamp(fBounciness, 0.0, 1.0); + } + + ActivitiesFactory::CommonParameters const aParms(fillCommonParameters()); + return ActivitiesFactory::createSimpleActivity( + aParms, + AnimationFactory::createPhysicsAnimation( + getContext().mpBox2DWorld, fDuration, getContext().mpSubsettableShapeManager, + getSlideSize(), { fStartVelocityX, fStartVelocityY }, fDensity, fBounciness, 0), + true); +} + +bool AnimationPhysicsNode::enqueueActivity() const +{ + return getContext().mrActivitiesQueue.addTailActivity(mpActivity); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationphysicsnode.hxx b/slideshow/source/engine/animationnodes/animationphysicsnode.hxx new file mode 100644 index 000000000..78298b23b --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationphysicsnode.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include "animationbasenode.hxx" +#include <com/sun/star/animations/XAnimatePhysics.hpp> + +namespace slideshow +{ +namespace internal +{ +class AnimationPhysicsNode : public AnimationBaseNode +{ +public: + AnimationPhysicsNode(const css::uno::Reference<css::animations::XAnimationNode>& xNode, + const BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext) + : AnimationBaseNode(xNode, rParent, rContext) + , mxPhysicsMotionNode(xNode, css::uno::UNO_QUERY_THROW) + { + } + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override { return "AnimationPhysicsNode"; } +#endif + +protected: + virtual void dispose() override; + +private: + virtual AnimationActivitySharedPtr createActivity() const override; + virtual bool enqueueActivity() const override; + + css::uno::Reference<css::animations::XAnimatePhysics> mxPhysicsMotionNode; +}; + +} // namespace internal +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationsetnode.cxx b/slideshow/source/engine/animationnodes/animationsetnode.cxx new file mode 100644 index 000000000..89747901e --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationsetnode.cxx @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <animationfactory.hxx> +#include "setactivity.hxx" +#include "animationsetnode.hxx" +#include "nodetools.hxx" +#include <tools.hxx> +#include <delayevent.hxx> + +using namespace com::sun::star; + +namespace slideshow::internal { + +AnimationActivitySharedPtr AnimationSetNode::createActivity() const +{ + ActivitiesFactory::CommonParameters aParms( fillCommonParameters() ); + uno::Reference<animations::XAnimate> const xAnimateNode = getXAnimateNode(); + OUString const attrName( xAnimateNode->getAttributeName() ); + AttributableShapeSharedPtr const pShape( getShape() ); + + // make deactivation a two-step procedure. Normally, we + // could solely rely on + // BaseNode::scheduleDeactivationEvent() to deactivate() + // us. Unfortunately, that method on the one hand ignores + // indefinite timing, on the other hand generates + // zero-timeout delays, which might get fired _before_ our + // set activity has taken place. Therefore, we enforce + // sequentiality by letting only the set activity schedule + // the deactivation event (and AnimationBaseNode + // takes care for the fact when mpActivity should be zero). + + // AnimationBaseNode::fillCommonParameters() has set up + // immediate deactivation as default when activity ends, but + if (! isIndefiniteTiming( xAnimateNode->getDuration() )) { + std::shared_ptr<AnimationSetNode> const pSelf( + std::dynamic_pointer_cast<AnimationSetNode>(getSelf()) ); + ENSURE_OR_THROW( + pSelf, "cannot cast getSelf() to my type!" ); + aParms.mpEndEvent = makeEvent( + [pSelf] () { pSelf->scheduleDeactivationEvent(); }, + "AnimationSetNode::scheduleDeactivationEvent"); + } + + switch (AnimationFactory::classifyAttributeName( attrName )) { + default: + case AnimationFactory::CLASS_UNKNOWN_PROPERTY: + ENSURE_OR_THROW( + false, "AnimationSetNode::createSetActivity(): " + "Unexpected attribute class" ); + break; + + case AnimationFactory::CLASS_NUMBER_PROPERTY: + { + NumberAnimation::ValueType aValue; + + ENSURE_OR_THROW( + extractValue( aValue, + xAnimateNode->getTo(), + pShape, + getSlideSize() ), + "AnimationSetNode::createSetActivity(): " + "Could not import numeric to value" ); + + return makeSetActivity( + aParms, + AnimationFactory::createNumberPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, + AnimationFactory::FLAG_NO_SPRITE ), + aValue ); + } + + case AnimationFactory::CLASS_ENUM_PROPERTY: + { + EnumAnimation::ValueType aValue; + + ENSURE_OR_THROW( + extractValue( aValue, + xAnimateNode->getTo(), + pShape, + getSlideSize() ), + "AnimationSetNode::createSetActivity(): " + "Could not import enum to value" ); + + return makeSetActivity( + aParms, + AnimationFactory::createEnumPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, + AnimationFactory::FLAG_NO_SPRITE ), + aValue ); + } + + case AnimationFactory::CLASS_COLOR_PROPERTY: + { + ColorAnimation::ValueType aValue; + + ENSURE_OR_THROW( + extractValue( aValue, + xAnimateNode->getTo(), + pShape, + getSlideSize() ), + "AnimationSetNode::createSetActivity(): " + "Could not import color to value" ); + + return makeSetActivity( + aParms, + AnimationFactory::createColorPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, + AnimationFactory::FLAG_NO_SPRITE ), + aValue ); + } + + case AnimationFactory::CLASS_STRING_PROPERTY: + { + StringAnimation::ValueType aValue; + + ENSURE_OR_THROW( + extractValue( aValue, + xAnimateNode->getTo(), + pShape, + getSlideSize() ), + "AnimationSetNode::createSetActivity(): " + "Could not import string to value" ); + + return makeSetActivity( + aParms, + AnimationFactory::createStringPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, + AnimationFactory::FLAG_NO_SPRITE ), + aValue ); + } + + case AnimationFactory::CLASS_BOOL_PROPERTY: + { + BoolAnimation::ValueType aValue; + + ENSURE_OR_THROW( + extractValue( aValue, + xAnimateNode->getTo(), + pShape, + getSlideSize() ), + "AnimationSetNode::createSetActivity(): " + "Could not import bool to value" ); + + return makeSetActivity( + aParms, + AnimationFactory::createBoolPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, + AnimationFactory::FLAG_NO_SPRITE ), + aValue ); + } + } + + return AnimationActivitySharedPtr(); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationsetnode.hxx b/slideshow/source/engine/animationnodes/animationsetnode.hxx new file mode 100644 index 000000000..0c1625560 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationsetnode.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX + +#include "animationbasenode.hxx" + +namespace slideshow::internal { + +class AnimationSetNode : public AnimationBaseNode +{ +public: + AnimationSetNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) + : AnimationBaseNode( xNode, pParent, rContext ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override { return "AnimationSetNode"; } +#endif + +private: + virtual AnimationActivitySharedPtr createActivity() const override; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationtransformnode.cxx b/slideshow/source/engine/animationnodes/animationtransformnode.cxx new file mode 100644 index 000000000..cbb68b024 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationtransformnode.cxx @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/animations/AnimationTransformType.hpp> + +#include "animationtransformnode.hxx" +#include <animationfactory.hxx> +#include <activitiesfactory.hxx> + +using namespace com::sun::star; + +namespace slideshow::internal { + +void AnimationTransformNode::dispose() +{ + mxTransformNode.clear(); + AnimationBaseNode::dispose(); +} + +AnimationActivitySharedPtr AnimationTransformNode::createActivity() const +{ + ActivitiesFactory::CommonParameters aParms( fillCommonParameters() ); + + const sal_Int16 nTransformType( mxTransformNode->getTransformType() ); + + const AttributableShapeSharedPtr& rShape( getShape() ); + + switch( nTransformType ) + { + default: + throw css::uno::RuntimeException( + "AnimationTransformNode::createTransformActivity(): " + "Unknown transform type" ); + + case animations::AnimationTransformType::TRANSLATE: + case animations::AnimationTransformType::SCALE: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createPairPropertyAnimation( + rShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + nTransformType, 0 ), + getXAnimateNode() ); + + case animations::AnimationTransformType::ROTATE: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createNumberPropertyAnimation( + "Rotate", + rShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + getXAnimateNode() ); + + case animations::AnimationTransformType::SKEWX: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createNumberPropertyAnimation( + "SkewX", + rShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + getXAnimateNode() ); + + case animations::AnimationTransformType::SKEWY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createNumberPropertyAnimation( + "SkewY", + rShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + getXAnimateNode() ); + } +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationtransformnode.hxx b/slideshow/source/engine/animationnodes/animationtransformnode.hxx new file mode 100644 index 000000000..1eafe2dc9 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationtransformnode.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX + +#include "animationbasenode.hxx" +#include <com/sun/star/animations/XAnimateTransform.hpp> + +namespace slideshow::internal { + +class AnimationTransformNode : public AnimationBaseNode +{ +public: + AnimationTransformNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) + : AnimationBaseNode( xNode, pParent, rContext ), + mxTransformNode( xNode, css::uno::UNO_QUERY_THROW ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override + { return "AnimationTransformNode"; } +#endif + +protected: + virtual void dispose() override; + +private: + virtual AnimationActivitySharedPtr createActivity() const override; + + css::uno::Reference<css::animations::XAnimateTransform > mxTransformNode; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx b/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx new file mode 100644 index 000000000..13a62d89b --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "animationtransitionfilternode.hxx" +#include <transitionfactory.hxx> + +namespace slideshow::internal { + +void AnimationTransitionFilterNode::dispose() +{ + mxTransitionFilterNode.clear(); + AnimationBaseNode::dispose(); +} + +AnimationActivitySharedPtr +AnimationTransitionFilterNode::createActivity() const +{ + return TransitionFactory::createShapeTransition( + fillCommonParameters(), + getShape(), + getContext().mpSubsettableShapeManager, + getSlideSize(), + mxTransitionFilterNode ); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx b/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx new file mode 100644 index 000000000..181899557 --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX + +#include "animationbasenode.hxx" +#include <com/sun/star/animations/XTransitionFilter.hpp> + +namespace slideshow::internal { + +class AnimationTransitionFilterNode : public AnimationBaseNode +{ +public: + AnimationTransitionFilterNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) + : AnimationBaseNode( xNode, pParent, rContext ), + mxTransitionFilterNode( xNode, css::uno::UNO_QUERY_THROW) + {} + +#if OSL_DEBUG_LEVEL >= 2 + virtual const char* getDescription() const + { return "AnimationTransitionFilterNode"; } +#endif + +protected: + virtual void dispose() override; + +private: + virtual AnimationActivitySharedPtr createActivity() const override; + + css::uno::Reference<css::animations::XTransitionFilter> mxTransitionFilterNode; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/basecontainernode.cxx b/slideshow/source/engine/animationnodes/basecontainernode.cxx new file mode 100644 index 000000000..709f5392a --- /dev/null +++ b/slideshow/source/engine/animationnodes/basecontainernode.cxx @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <basecontainernode.hxx> +#include <com/sun/star/animations/AnimationRestart.hpp> +#include <com/sun/star/animations/AnimationFill.hpp> +#include <eventqueue.hxx> +#include "nodetools.hxx" +#include <delayevent.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <functional> +#include <algorithm> + +using namespace com::sun::star; + +namespace slideshow::internal { +namespace { +bool isRepeatIndefinite(const uno::Reference<animations::XAnimationNode>& xNode) +{ + return xNode->getRepeatCount().hasValue() && isIndefiniteTiming(xNode->getRepeatCount()); +} + +bool isRestart(const uno::Reference<animations::XAnimationNode>& xNode) +{ + sal_Int16 nRestart = xNode->getRestart(); + return nRestart == animations::AnimationRestart::WHEN_NOT_ACTIVE || + nRestart == animations::AnimationRestart::ALWAYS; +} +} + +BaseContainerNode::BaseContainerNode( + const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : BaseNode( xNode, rParent, rContext ), + maChildren(), + mnFinishedChildren(0), + mnLeftIterations(0), + mbRepeatIndefinite(isRepeatIndefinite(xNode)), + mbRestart(isRestart(xNode)), + mbDurationIndefinite( isIndefiniteTiming( xNode->getEnd() ) && + isIndefiniteTiming( xNode->getDuration() ) ) +{ +} + +void BaseContainerNode::dispose() +{ + forEachChildNode( std::mem_fn(&Disposable::dispose), -1 ); + maChildren.clear(); + BaseNode::dispose(); +} + +bool BaseContainerNode::init_st() +{ + if( !(getXAnimationNode()->getRepeatCount() >>= mnLeftIterations) ) + mnLeftIterations = 1.0; + return init_children(); +} + +bool BaseContainerNode::init_children() +{ + mnFinishedChildren = 0; + + // initialize all children + return (o3tl::make_unsigned(std::count_if( + maChildren.begin(), maChildren.end(), + std::mem_fn(&AnimationNode::init) )) == + maChildren.size()); +} + +void BaseContainerNode::deactivate_st( NodeState eDestState ) +{ + mnLeftIterations = 0; // in order to make skip effect work correctly + if (eDestState == FROZEN) { + // deactivate all children that are not FROZEN or ENDED: + forEachChildNode( std::mem_fn(&AnimationNode::deactivate), + ~(FROZEN | ENDED) ); + } + else { + // end all children that are not ENDED: + forEachChildNode( std::mem_fn(&AnimationNode::end), ~ENDED ); + } +} + +bool BaseContainerNode::hasPendingAnimation() const +{ + // does any of our children returns "true" on + // AnimationNode::hasPendingAnimation()? + // If yes, we, too, return true + return std::any_of( + maChildren.begin(), maChildren.end(), + std::mem_fn(&AnimationNode::hasPendingAnimation) ); +} + +void BaseContainerNode::appendChildNode( AnimationNodeSharedPtr const& pNode ) +{ + if (! checkValidNode()) + return; + + // register derived classes as end listeners at all children. + // this is necessary to control the children animation + // sequence, and to determine our own end event + if (pNode->registerDeactivatingListener( getSelf() )) { + maChildren.push_back( pNode ); + } +} + +bool BaseContainerNode::isChildNode( AnimationNodeSharedPtr const& pNode ) const +{ + // find given notifier in child vector + VectorOfNodes::const_iterator const iEnd( maChildren.end() ); + VectorOfNodes::const_iterator const iFind( + std::find( maChildren.begin(), iEnd, pNode ) ); + return (iFind != iEnd); +} + +bool BaseContainerNode::notifyDeactivatedChild( + AnimationNodeSharedPtr const& pChildNode ) +{ + OSL_ASSERT( pChildNode->getState() == FROZEN || + pChildNode->getState() == ENDED ); + // early exit on invalid nodes + OSL_ASSERT( getState() != INVALID ); + if( getState() == INVALID ) + return false; + + if (! isChildNode(pChildNode)) { + OSL_FAIL( "unknown notifier!" ); + return false; + } + + std::size_t const nSize = maChildren.size(); + OSL_ASSERT( mnFinishedChildren < nSize ); + ++mnFinishedChildren; + bool bFinished = (mnFinishedChildren >= nSize); + + // Handle repetition here. + if (bFinished) { + if(!mbRepeatIndefinite && mnLeftIterations >= 1.0) + { + mnLeftIterations -= 1.0; + } + if(mnLeftIterations >= 1.0 || mbRestart) + { + // SMIL spec said that "Accumulate" controls whether or not the animation + // is cumulative, but XTimeContainer do not have this attribute, so always + // remove the effect before next repeat. + forEachChildNode(std::mem_fn(&AnimationNode::removeEffect), -1); + + if (mnLeftIterations >= 1.0) + bFinished = false; + + EventSharedPtr aRepetitionEvent = + makeDelay( [this] () { this->repeat(); }, + 0.0, + "BaseContainerNode::repeat"); + getContext().mrEventQueue.addEvent( aRepetitionEvent ); + } + else if (isDurationIndefinite()) + { + if (getFillMode() == animations::AnimationFill::REMOVE) + forEachChildNode(std::mem_fn(&AnimationNode::removeEffect), -1); + deactivate(); + } + } + + return bFinished; +} + +void BaseContainerNode::repeat() +{ + // Prevent repeat event scheduled before deactivation. + if (getState() == FROZEN || getState() == ENDED) + return; + + forEachChildNode( std::mem_fn(&AnimationNode::end), ~ENDED ); + bool bState = init_children(); + if( bState ) + activate_st(); +} + +#if defined(DBG_UTIL) +void BaseContainerNode::showState() const +{ + for(const auto & i : maChildren) + { + BaseNodeSharedPtr pNode = + std::dynamic_pointer_cast<BaseNode>(i); + SAL_INFO("slideshow.verbose", + "Node connection: n" << + debugGetNodeName(this) << + " -> n" << + debugGetNodeName(pNode.get())); + pNode->showState(); + } + + BaseNode::showState(); +} +#endif + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/basenode.cxx b/slideshow/source/engine/animationnodes/basenode.cxx new file mode 100644 index 000000000..5e98b63bb --- /dev/null +++ b/slideshow/source/engine/animationnodes/basenode.cxx @@ -0,0 +1,751 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/animations/XAnimate.hpp> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <com/sun/star/animations/AnimationFill.hpp> +#include <com/sun/star/animations/AnimationRestart.hpp> +#include <com/sun/star/presentation/EffectNodeType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <basenode.hxx> +#include <eventmultiplexer.hxx> +#include <basecontainernode.hxx> +#include <eventqueue.hxx> +#include <delayevent.hxx> +#include <tools.hxx> +#include "nodetools.hxx" +#include "generateevent.hxx" + +#include <sal/log.hxx> + +#include <vector> +#include <algorithm> + +using namespace ::com::sun::star; + +namespace slideshow::internal { + +namespace { + +typedef int StateTransitionTable[17]; + +// State transition tables +// ========================================================================= + +const int* getStateTransitionTable( sal_Int16 nRestartMode, + sal_Int16 nFillMode ) +{ + // TODO(F2): restart issues in below tables + + // transition table for restart=NEVER, fill=REMOVE + static const StateTransitionTable stateTransitionTable_Never_Remove = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::ENDED, // active successors for ACTIVE: no freeze here + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, // active successors for FROZEN: this state is unreachable here + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED // active successors for ENDED: this state is a sink here (cannot restart) + }; + + // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE + static const StateTransitionTable stateTransitionTable_NotActive_Remove = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::ENDED, // active successors for ACTIVE: no freeze here + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, // active successors for FROZEN: + // this state is unreachable here + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED: + // restart possible when ended + }; + + // transition table for restart=ALWAYS, fill=REMOVE + static const StateTransitionTable stateTransitionTable_Always_Remove = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE: restart + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, // active successors for FROZEN: + // this state is unreachable here + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart + }; + + // transition table for restart=NEVER, fill=FREEZE + static const StateTransitionTable stateTransitionTable_Never_Freeze = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED, // active successors for FROZEN: end + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED, // active successors for ENDED: this state is a sink here (cannot restart) + }; + + // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE + static const StateTransitionTable stateTransitionTable_NotActive_Freeze = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN: + // restart possible when ended + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED: + // restart possible when ended + }; + + // transition table for restart=ALWAYS, fill=FREEZE + static const StateTransitionTable stateTransitionTable_Always_Freeze = { + AnimationNode::INVALID, + AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED + AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED + AnimationNode::INVALID, + AnimationNode::FROZEN|AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE: + // end object, restart + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN: restart possible + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::INVALID, + AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart + }; + + static const StateTransitionTable* tableGuide[] = { + &stateTransitionTable_Never_Remove, + &stateTransitionTable_NotActive_Remove, + &stateTransitionTable_Always_Remove, + &stateTransitionTable_Never_Freeze, + &stateTransitionTable_NotActive_Freeze, + &stateTransitionTable_Always_Freeze + }; + + int nRestartValue; + switch( nRestartMode ) { + default: + case animations::AnimationRestart::DEFAULT: + // same value: animations::AnimationRestart::INHERIT: + OSL_FAIL( + "getStateTransitionTable(): unexpected case for restart" ); + [[fallthrough]]; + case animations::AnimationRestart::NEVER: + nRestartValue = 0; + break; + case animations::AnimationRestart::WHEN_NOT_ACTIVE: + nRestartValue = 1; + break; + case animations::AnimationRestart::ALWAYS: + nRestartValue = 2; + break; + } + + int nFillValue; + switch( nFillMode ) { + default: + case animations::AnimationFill::AUTO: + case animations::AnimationFill::DEFAULT: + // same value: animations::AnimationFill::INHERIT: + OSL_FAIL( + "getStateTransitionTable(): unexpected case for fill" ); + [[fallthrough]]; + case animations::AnimationFill::REMOVE: + nFillValue = 0; + break; + case animations::AnimationFill::FREEZE: + case animations::AnimationFill::HOLD: + case animations::AnimationFill::TRANSITION: + nFillValue = 1; + break; + } + + return *tableGuide[ 3*nFillValue + nRestartValue ]; +} + +/// Little helper predicate, to detect main sequence root node +bool isMainSequenceRootNode_( + const uno::Reference< animations::XAnimationNode >& xNode ) +{ + // detect main sequence root node (need that for + // end-of-mainsequence signalling below) + beans::NamedValue const aSearchKey( + "node-type", + uno::Any( presentation::EffectNodeType::MAIN_SEQUENCE ) ); + + uno::Sequence<beans::NamedValue> const userData(xNode->getUserData()); + return findNamedValue( userData, aSearchKey ); +} + +} // anon namespace + +// BaseNode implementation +//========================================================================= + +/** state transition handling + */ +class BaseNode::StateTransition +{ +public: + enum class Options { NONE, FORCE }; + + explicit StateTransition( BaseNode * pNode ) + : mpNode(pNode), meToState(INVALID) {} + + ~StateTransition() { + clear(); + } + + StateTransition(const StateTransition&) = delete; + StateTransition& operator=(const StateTransition&) = delete; + + bool enter( NodeState eToState, Options options = Options::NONE ) + { + OSL_ENSURE( meToState == INVALID, + "### commit() before enter()ing again!" ); + if (meToState != INVALID) + return false; + bool const bForce = options == Options::FORCE; + if (!bForce && !mpNode->isTransition( mpNode->meCurrState, eToState )) + return false; + // recursion detection: + if ((mpNode->meCurrentStateTransition & eToState) != 0) + return false; // already in wanted transition + // mark transition: + mpNode->meCurrentStateTransition |= eToState; + meToState = eToState; + return true; // in transition + } + + void commit() { + OSL_ENSURE( meToState != INVALID, "### nothing to commit!" ); + if (meToState != INVALID) { + mpNode->meCurrState = meToState; + clear(); + } + } + + void clear() { + if (meToState != INVALID) { + OSL_ASSERT( (mpNode->meCurrentStateTransition & meToState) != 0 ); + mpNode->meCurrentStateTransition &= ~meToState; + meToState = INVALID; + } + } + +private: + BaseNode *const mpNode; + NodeState meToState; +}; + +BaseNode::BaseNode( const uno::Reference< animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) : + maContext( rContext.maContext ), + maDeactivatingListeners(), + mxAnimationNode( xNode ), + mpParent( rParent ), + mpSelf(), + mpStateTransitionTable( nullptr ), + mnStartDelay( rContext.mnStartDelay ), + meCurrState( UNRESOLVED ), + meCurrentStateTransition( 0 ), + mpCurrentEvent(), + mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode ) ) +{ + ENSURE_OR_THROW( mxAnimationNode.is(), + "BaseNode::BaseNode(): Invalid XAnimationNode" ); + + // setup state transition table + mpStateTransitionTable = getStateTransitionTable( getRestartMode(), + getFillMode() ); +} + +void BaseNode::dispose() +{ + meCurrState = INVALID; + + // discharge a loaded event, if any: + if (mpCurrentEvent) { + mpCurrentEvent->dispose(); + mpCurrentEvent.reset(); + } + maDeactivatingListeners.clear(); + mxAnimationNode.clear(); + mpParent.reset(); + mpSelf.reset(); + maContext.dispose(); +} + + +sal_Int16 BaseNode::getRestartMode() +{ + const sal_Int16 nTmp( mxAnimationNode->getRestart() ); + return nTmp != animations::AnimationRestart::DEFAULT + ? nTmp : getRestartDefaultMode(); +} + +sal_Int16 BaseNode::getFillMode() +{ + const sal_Int16 nTmp( mxAnimationNode->getFill() ); + const sal_Int16 nFill(nTmp != animations::AnimationFill::DEFAULT + ? nTmp : getFillDefaultMode()); + + // For AUTO fill mode, SMIL specifies that fill mode is FREEZE, + // if no explicit active duration is given + // (no duration, end, repeatCount or repeatDuration given), + // and REMOVE otherwise + if( nFill == animations::AnimationFill::AUTO ) { + return (isIndefiniteTiming( mxAnimationNode->getDuration() ) && + isIndefiniteTiming( mxAnimationNode->getEnd() ) && + !mxAnimationNode->getRepeatCount().hasValue() && + isIndefiniteTiming( mxAnimationNode->getRepeatDuration() )) + ? animations::AnimationFill::FREEZE + : animations::AnimationFill::REMOVE; + } + else { + return nFill; + } +} + +sal_Int16 BaseNode::getFillDefaultMode() const +{ + sal_Int16 nFillDefault = mxAnimationNode->getFillDefault(); + if (nFillDefault == animations::AnimationFill::DEFAULT) { + nFillDefault = (mpParent != nullptr + ? mpParent->getFillDefaultMode() + : animations::AnimationFill::AUTO); + } + return nFillDefault; +} + +sal_Int16 BaseNode::getRestartDefaultMode() const +{ + sal_Int16 nRestartDefaultMode = mxAnimationNode->getRestartDefault(); + if (nRestartDefaultMode == animations::AnimationRestart::DEFAULT) { + nRestartDefaultMode = (mpParent != nullptr + ? mpParent->getRestartDefaultMode() + : animations::AnimationRestart::ALWAYS); + } + return nRestartDefaultMode; +} + +uno::Reference<animations::XAnimationNode> BaseNode::getXAnimationNode() const +{ + return mxAnimationNode; +} + +bool BaseNode::init() +{ + if (! checkValidNode()) + return false; + meCurrState = UNRESOLVED; + // discharge a loaded event, if any: + if (mpCurrentEvent) { + mpCurrentEvent->dispose(); + mpCurrentEvent.reset(); + } + return init_st(); // may call derived class +} + +bool BaseNode::init_st() +{ + return true; +} + +bool BaseNode::resolve() +{ + if (! checkValidNode()) + return false; + + OSL_ASSERT( meCurrState != RESOLVED ); + if (inStateOrTransition( RESOLVED )) + return true; + + StateTransition st(this); + if (st.enter( RESOLVED ) && + isTransition( RESOLVED, ACTIVE ) && + resolve_st() /* may call derived class */) + { + st.commit(); // changing state + + // discharge a loaded event, if any: + if (mpCurrentEvent) + mpCurrentEvent->dispose(); + + // schedule activation event: + + // This method takes the NodeContext::mnStartDelay value into account, + // to cater for iterate container time shifts. We cannot put different + // iterations of the iterate container's children into different + // subcontainer (such as a 'DelayContainer', which delays resolving its + // children by a fixed amount), since all iterations' nodes must be + // resolved at the same time (otherwise, the delayed subset creation + // will not work, i.e. deactivate the subsets too late in the master + // shape). + uno::Any const aBegin( mxAnimationNode->getBegin() ); + if (aBegin.hasValue()) { + auto self(mpSelf); + mpCurrentEvent = generateEvent( + aBegin, [self] () { self->activate(); }, + maContext, mnStartDelay ); + } + else { + // For some leaf nodes, PPT import yields empty begin time, + // although semantically, it should be 0.0 + // TODO(F3): That should really be provided by the PPT import + + // schedule delayed activation event. Take iterate node + // timeout into account + auto self(mpSelf); + mpCurrentEvent = makeDelay( + [self] () { self->activate(); }, + mnStartDelay, + "AnimationNode::activate with delay"); + maContext.mrEventQueue.addEvent( mpCurrentEvent ); + } + + return true; + } + return false; +} + +bool BaseNode::resolve_st() +{ + return true; +} + + +void BaseNode::activate() +{ + if (! checkValidNode()) + return; + + OSL_ASSERT( meCurrState != ACTIVE ); + if (inStateOrTransition( ACTIVE )) + return; + + StateTransition st(this); + if (st.enter( ACTIVE )) { + + activate_st(); // calling derived class + + st.commit(); // changing state + + maContext.mrEventMultiplexer.notifyAnimationStart( mpSelf ); + } +} + +void BaseNode::activate_st() +{ + scheduleDeactivationEvent(); +} + +void BaseNode::scheduleDeactivationEvent( EventSharedPtr const& pEvent ) +{ + if (mpCurrentEvent) { + mpCurrentEvent->dispose(); + mpCurrentEvent.reset(); + } + if (pEvent) { + if (maContext.mrEventQueue.addEvent( pEvent )) + mpCurrentEvent = pEvent; + } + else { + // This method need not take the + // NodeContext::mnStartDelay value into account, + // because the deactivation event is only scheduled + // when the effect is started: the timeout is then + // already respected. + + // xxx todo: + // think about set node, anim base node! + // if anim base node has no activity, this is called to schedule deactivation, + // but what if it does not schedule anything? + + auto self(mpSelf); + if (mxAnimationNode->getEnd().hasValue()) + { + // TODO: We may need to calculate the duration if the end value is numeric. + // We expect that the end value contains EventTrigger::ON_NEXT here. + // LibreOffice does not generate numeric values, so we can leave it + // until we find a test case. + mpCurrentEvent = generateEvent( + mxAnimationNode->getEnd(), + [self] () { self->deactivate(); }, + maContext, 0.0 ); + + } + else + { + mpCurrentEvent = generateEvent( + mxAnimationNode->getDuration(), + [self] () { self->deactivate(); }, + maContext, 0.0 ); + } + } +} + +void BaseNode::deactivate() +{ + if (inStateOrTransition( ENDED | FROZEN ) || !checkValidNode()) + return; + + if (isTransition( meCurrState, FROZEN, false /* no OSL_ASSERT */ )) { + // do transition to FROZEN: + StateTransition st(this); + if (st.enter( FROZEN, StateTransition::Options::FORCE )) { + + deactivate_st( FROZEN ); + st.commit(); + + notifyEndListeners(); + + // discharge a loaded event, before going on: + if (mpCurrentEvent) { + mpCurrentEvent->dispose(); + mpCurrentEvent.reset(); + } + } + } + else { + // use end instead: + end(); + } + // state has changed either to FROZEN or ENDED +} + +void BaseNode::deactivate_st( NodeState ) +{ +} + +void BaseNode::end() +{ + bool const bIsFrozenOrInTransitionToFrozen = inStateOrTransition( FROZEN ); + if (inStateOrTransition( ENDED ) || !checkValidNode()) + return; + + // END must always be reachable. If not, that's an error in the + // transition tables + OSL_ENSURE( isTransition( meCurrState, ENDED ), + "end state not reachable in transition table" ); + + StateTransition st(this); + if (!st.enter( ENDED, StateTransition::Options::FORCE )) + return; + + deactivate_st( ENDED ); + st.commit(); // changing state + + // if is FROZEN or is to be FROZEN, then + // will/already notified deactivating listeners + if (!bIsFrozenOrInTransitionToFrozen) + notifyEndListeners(); + + // discharge a loaded event, before going on: + if (mpCurrentEvent) { + mpCurrentEvent->dispose(); + mpCurrentEvent.reset(); + } +} + +void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr& rNotifier ) +{ + OSL_ASSERT( rNotifier->getState() == FROZEN || + rNotifier->getState() == ENDED ); + // TODO(F1): for end sync functionality, this might indeed be used some day +} + +void BaseNode::notifyEndListeners() const +{ + // notify all listeners + for( const auto& rListener : maDeactivatingListeners ) + rListener->notifyDeactivating( mpSelf ); + + // notify state change + maContext.mrEventMultiplexer.notifyAnimationEnd( mpSelf ); + + // notify main sequence end (iff we're the main + // sequence root node). This is because the main + // sequence determines the active duration of the + // slide. All other sequences are secondary, in that + // they don't prevent a slide change from happening, + // even if they have not been completed. In other + // words, all sequences except the main sequence are + // optional for the slide lifetime. + if (isMainSequenceRootNode()) + maContext.mrEventMultiplexer.notifySlideAnimationsEnd(); +} + +AnimationNode::NodeState BaseNode::getState() const +{ + return meCurrState; +} + +bool BaseNode::registerDeactivatingListener( + const AnimationNodeSharedPtr& rNotifee ) +{ + if (! checkValidNode()) + return false; + + ENSURE_OR_RETURN_FALSE( + rNotifee, + "BaseNode::registerDeactivatingListener(): invalid notifee" ); + maDeactivatingListeners.push_back( rNotifee ); + + return true; +} + +void BaseNode::setSelf( const BaseNodeSharedPtr& rSelf ) +{ + ENSURE_OR_THROW( rSelf.get() == this, + "BaseNode::setSelf(): got ptr to different object" ); + ENSURE_OR_THROW( !mpSelf, + "BaseNode::setSelf(): called multiple times" ); + + mpSelf = rSelf; +} + +// Debug + + +#if defined(DBG_UTIL) +void BaseNode::showState() const +{ + const AnimationNode::NodeState eNodeState( getState() ); + + if( eNodeState == AnimationNode::INVALID ) + SAL_INFO("slideshow.verbose", "Node state: n" << + debugGetNodeName(this) << + " [label=\"" << + getDescription() << + "\",style=filled, fillcolor=\"0.5,0.2,0.5\"]"); + else + SAL_INFO("slideshow.verbose", "Node state: n" << + debugGetNodeName(this) << + " [label=\"" << + getDescription() << + "fillcolor=\"" << + log(double(getState()))/4.0 << + ",1.0,1.0\"]"); + + // determine additional node information + uno::Reference<animations::XAnimate> const xAnimate( mxAnimationNode, + uno::UNO_QUERY ); + if( !xAnimate.is() ) + return; + + uno::Reference< drawing::XShape > xTargetShape( xAnimate->getTarget(), + uno::UNO_QUERY ); + + if( !xTargetShape.is() ) + { + css::presentation::ParagraphTarget aTarget; + + // no shape provided. Maybe a ParagraphTarget? + if( xAnimate->getTarget() >>= aTarget ) + xTargetShape = aTarget.Shape; + } + + if( !xTargetShape.is() ) + return; + + uno::Reference< beans::XPropertySet > xPropSet( xTargetShape, + uno::UNO_QUERY ); + + // read shape name + OUString aName; + if( xPropSet->getPropertyValue("Name") >>= aName ) + { + SAL_INFO("slideshow.verbose", "Node info: n" << + debugGetNodeName(this) << + ", name \"" << + aName << + "\""); + } +} + +const char* BaseNode::getDescription() const +{ + return "BaseNode"; +} + +#endif + +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/generateevent.cxx b/slideshow/source/engine/animationnodes/generateevent.cxx new file mode 100644 index 000000000..6d61c0c25 --- /dev/null +++ b/slideshow/source/engine/animationnodes/generateevent.cxx @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <tools/diagnose_ex.h> + +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/animations/XAnimationNode.hpp> +#include <com/sun/star/animations/Timing.hpp> +#include <com/sun/star/animations/EventTrigger.hpp> +#include <com/sun/star/animations/Event.hpp> + +#include "generateevent.hxx" +#include <subsettableshapemanager.hxx> +#include <usereventqueue.hxx> +#include <slideshowcontext.hxx> +#include <delayevent.hxx> + +namespace slideshow::internal { + +using namespace com::sun::star; + +EventSharedPtr generateEvent( + uno::Any const& rEventDescription, + Delay::FunctorT const& rFunctor, + SlideShowContext const& rContext, + double nAdditionalDelay ) +{ + EventSharedPtr pEvent; + + if (! rEventDescription.hasValue()) + return pEvent; + + animations::Timing eTiming; + animations::Event aEvent; + uno::Sequence<uno::Any> aSequence; + double nDelay1 = 0; + + if (rEventDescription >>= eTiming) { + switch (eTiming) { + case animations::Timing_INDEFINITE: + break; // don't schedule no event + case animations::Timing_MEDIA: + OSL_FAIL( "MEDIA timing not yet implemented!" ); + break; + default: + ENSURE_OR_THROW( false, "unexpected case!" ); + } + } + else if (rEventDescription >>= aEvent) { + + // try to extract additional event delay + double nDelay2 = 0.0; + if (aEvent.Offset.hasValue() && !(aEvent.Offset >>= nDelay2)) { + OSL_FAIL( "offset values apart from DOUBLE not " + "recognized in animations::Event!" ); + } + + // common vars used inside switch + uno::Reference<animations::XAnimationNode> xNode; + uno::Reference<drawing::XShape> xShape; + ShapeSharedPtr pShape; + + // TODO(F1): Respect aEvent.Repeat value + + auto event2shape = [&] () { + if (aEvent.Source >>= xShape) + pShape = rContext.mpSubsettableShapeManager->lookupShape(xShape); + }; + + switch (aEvent.Trigger) { + default: + ENSURE_OR_THROW( false, "unexpected event trigger!" ); + case animations::EventTrigger::NONE: + // no event at all + break; + case animations::EventTrigger::ON_BEGIN: + OSL_FAIL( "event trigger ON_BEGIN not yet implemented!" ); + break; + case animations::EventTrigger::ON_END: + OSL_FAIL( "event trigger ON_END not yet implemented!" ); + break; + case animations::EventTrigger::BEGIN_EVENT: + // try to extract XAnimationNode event source + if (aEvent.Source >>= xNode) { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, BEGIN_EVENT"); + rContext.mrUserEventQueue.registerAnimationStartEvent( + pEvent, xNode ); + } + else { + OSL_FAIL("could not extract source XAnimationNode " + "for BEGIN_EVENT!" ); + } + break; + case animations::EventTrigger::END_EVENT: + // try to extract XAnimationNode event source + if (aEvent.Source >>= xNode) { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, END_EVENT"); + rContext.mrUserEventQueue.registerAnimationEndEvent( + pEvent, xNode ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for END_EVENT!" ); + } + break; + case animations::EventTrigger::ON_CLICK: + // try to extract XShape event source + event2shape(); + if (pShape) + { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_CLICK"); + rContext.mrUserEventQueue.registerShapeClickEvent( + pEvent, pShape ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for ON_CLICK!" ); + } + break; + case animations::EventTrigger::ON_DBL_CLICK: + // try to extract XShape event source + event2shape(); + if (pShape) + { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_DBL_CLICK"); + rContext.mrUserEventQueue.registerShapeDoubleClickEvent( + pEvent, pShape ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for ON_DBL_CLICK!" ); + } + break; + case animations::EventTrigger::ON_MOUSE_ENTER: + // try to extract XShape event source + event2shape(); + if (pShape) + { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_MOUSE_ENTER"); + rContext.mrUserEventQueue.registerMouseEnterEvent( + pEvent, pShape ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for ON_MOUSE_ENTER!" ); + } + break; + case animations::EventTrigger::ON_MOUSE_LEAVE: + // try to extract XShape event source + event2shape(); + if (pShape) + { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_MOUSE_LEAVE"); + rContext.mrUserEventQueue.registerMouseLeaveEvent( + pEvent, pShape ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for ON_MOUSE_LEAVE!" ); + } + break; + case animations::EventTrigger::ON_PREV: + OSL_FAIL( "event trigger ON_PREV not yet implemented, " + "mapped to ON_NEXT!" ); + [[fallthrough]]; + case animations::EventTrigger::ON_NEXT: + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_NEXT"); + rContext.mrUserEventQueue.registerNextEffectEvent( pEvent ); + break; + case animations::EventTrigger::ON_STOP_AUDIO: + // try to extract XAnimationNode event source + if (aEvent.Source >>= xNode) { + pEvent = makeDelay( rFunctor, + nDelay2 + nAdditionalDelay, + "generateEvent, ON_STOP_AUDIO"); + rContext.mrUserEventQueue.registerAudioStoppedEvent( + pEvent, xNode ); + } + else { + OSL_FAIL( "could not extract source XAnimationNode " + "for ON_STOP_AUDIO!" ); + } + break; + case animations::EventTrigger::REPEAT: + OSL_FAIL( "event trigger REPEAT not yet implemented!" ); + break; + } + } + else if (rEventDescription >>= aSequence) { + OSL_FAIL( "sequence of timing primitives " + "not yet implemented!" ); + } + else if (rEventDescription >>= nDelay1) { + pEvent = makeDelay( rFunctor, + nDelay1 + nAdditionalDelay, + "generateEvent with delay"); + // schedule delay event + rContext.mrEventQueue.addEvent( pEvent ); + } + + return pEvent; +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/generateevent.hxx b/slideshow/source/engine/animationnodes/generateevent.hxx new file mode 100644 index 000000000..bf7ac99c1 --- /dev/null +++ b/slideshow/source/engine/animationnodes/generateevent.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX + +#include <slideshowcontext.hxx> +#include <delayevent.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace slideshow::internal { + +/** Create an event for the given description, calling the given functor. + + @param rEventDescription + Directly from API + + @param rFunctor + Functor to call when event fires. + + @param rContext + Context struct, to provide event queue + + @param nAdditionalDelay + Additional delay, gets added on top of timeout. +*/ +EventSharedPtr generateEvent( + css::uno::Any const& rEventDescription, + Delay::FunctorT const& rFunctor, + SlideShowContext const& rContext, + double nAdditionalDelay ); + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/nodetools.cxx b/slideshow/source/engine/animationnodes/nodetools.cxx new file mode 100644 index 000000000..f509e680c --- /dev/null +++ b/slideshow/source/engine/animationnodes/nodetools.cxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/animations/Timing.hpp> + +#include "nodetools.hxx" + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ +#if defined(DBG_UTIL) + static sal_Int32 lcl_nOffset = 0; + + OUString debugGetNodeName( const BaseNode *pNode ) + { + return OUString::number(lcl_nOffset) + + " - 0x" + + OUString::number(reinterpret_cast<sal_Int64>(pNode), 16); + } + + void debugNodesShowTree( const BaseNode* pNode ) + { + if( pNode ) + pNode->showState(); + + ++lcl_nOffset; + } +#endif + + AttributableShapeSharedPtr lookupAttributableShape( const ShapeManagerSharedPtr& rShapeManager, + const uno::Reference< drawing::XShape >& xShape ) + { + ENSURE_OR_THROW( rShapeManager, + "lookupAttributableShape(): invalid ShapeManager" ); + + ShapeSharedPtr pShape( rShapeManager->lookupShape( xShape ) ); + + ENSURE_OR_THROW( pShape, + "lookupAttributableShape(): no shape found for given XShape" ); + + AttributableShapeSharedPtr pRes( + ::std::dynamic_pointer_cast< AttributableShape >( pShape ) ); + + // TODO(E3): Cannot throw here, people might set animation info + // for non-animatable shapes from the API. AnimationNodes must catch + // the exception and handle that differently + ENSURE_OR_THROW( pRes, + "lookupAttributableShape(): shape found does not implement AttributableShape interface" ); + + return pRes; + } + + bool isIndefiniteTiming( const uno::Any& rAny ) + { + if( !rAny.hasValue() ) + return true; + + animations::Timing eTiming; + + return (rAny >>= eTiming) && eTiming == animations::Timing_INDEFINITE; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/nodetools.hxx b/slideshow/source/engine/animationnodes/nodetools.hxx new file mode 100644 index 000000000..63ddef786 --- /dev/null +++ b/slideshow/source/engine/animationnodes/nodetools.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX + +#include <com/sun/star/drawing/XShape.hpp> + +#include <shapemanager.hxx> +#include <basenode.hxx> +#include <attributableshape.hxx> + + +#if defined(DBG_UTIL) +# define DEBUG_NODES_SHOWTREE(a) debugNodesShowTree(a); +#else +# define DEBUG_NODES_SHOWTREE(a) +#endif + +namespace slideshow::internal + { + + // Tools + + +#if defined(DBG_UTIL) + OUString debugGetNodeName( const BaseNode *pNode ); + void debugNodesShowTree( const BaseNode* ); +#endif + + /** Look up an AttributableShape from ShapeManager. + + This method retrieves an AttributableShape pointer, given + an XShape and a LayerManager. + + Throws a runtime exception if there's no such shape, or if + it does not implement the AttributableShape interface. + */ + AttributableShapeSharedPtr lookupAttributableShape( const ShapeManagerSharedPtr& rShapeManager, + const css::uno::Reference< css::drawing::XShape >& xShape ); + + /** Predicate whether a Begin, Duration or End timing is + indefinite, i.e. either contains no value, or the + value Timing_INDEFINITE. + */ + bool isIndefiniteTiming( const css::uno::Any& rAny ); + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx b/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx new file mode 100644 index 000000000..f4c063716 --- /dev/null +++ b/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "paralleltimecontainer.hxx" +#include <delayevent.hxx> + +#include <functional> + +namespace slideshow::internal { + +void ParallelTimeContainer::activate_st() +{ + // resolve all children: + std::size_t const nResolvedNodes = + static_cast<std::size_t>(std::count_if( + maChildren.begin(), maChildren.end(), + std::mem_fn(&AnimationNode::resolve) )); + OSL_ENSURE( nResolvedNodes == maChildren.size(), + "### resolving all children failed!" ); + + if (isDurationIndefinite() && maChildren.empty()) { + // deactivate ASAP: + auto self(getSelf()); + scheduleDeactivationEvent( + makeEvent( [self] () { self->deactivate(); }, + "ParallelTimeContainer::deactivate") ); + } + else { // use default + scheduleDeactivationEvent(); + } +} + +void ParallelTimeContainer::notifyDeactivating( + AnimationNodeSharedPtr const& pChildNode ) +{ + notifyDeactivatedChild( pChildNode ); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx b/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx new file mode 100644 index 000000000..62daf2639 --- /dev/null +++ b/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX + +#include <basecontainernode.hxx> + +namespace slideshow::internal { + +/** This class implements parallel node containers + + All children of this node are played in parallel +*/ +class ParallelTimeContainer : public BaseContainerNode +{ +public: + ParallelTimeContainer( + const css::uno::Reference< css::animations::XAnimationNode >& xNode, + const BaseContainerNodeSharedPtr& rParent, + const NodeContext& rContext ) + : BaseContainerNode( xNode, rParent, rContext ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override + { return "ParallelTimeContainer"; } +#endif + +private: + virtual void activate_st() override; + virtual void notifyDeactivating( AnimationNodeSharedPtr const& pChildNode ) override; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/propertyanimationnode.cxx b/slideshow/source/engine/animationnodes/propertyanimationnode.cxx new file mode 100644 index 000000000..3e7d68b4b --- /dev/null +++ b/slideshow/source/engine/animationnodes/propertyanimationnode.cxx @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "propertyanimationnode.hxx" +#include <animationfactory.hxx> + +using namespace com::sun::star; + +namespace slideshow::internal { + +AnimationActivitySharedPtr PropertyAnimationNode::createActivity() const +{ + // Create AnimationActivity from common XAnimate parameters: + ActivitiesFactory::CommonParameters aParms( fillCommonParameters() ); + uno::Reference<animations::XAnimate> const& xAnimateNode =getXAnimateNode(); + OUString const attrName( xAnimateNode->getAttributeName() ); + AttributableShapeSharedPtr const pShape( getShape() ); + + switch (AnimationFactory::classifyAttributeName( attrName )) { + default: + case AnimationFactory::CLASS_UNKNOWN_PROPERTY: + ENSURE_OR_THROW( + false, + "Unexpected attribute class (unknown or empty attribute name)" ); + break; + + case AnimationFactory::CLASS_NUMBER_PROPERTY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createNumberPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + xAnimateNode ); + + case AnimationFactory::CLASS_ENUM_PROPERTY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createEnumPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, 0 ), + xAnimateNode ); + + case AnimationFactory::CLASS_COLOR_PROPERTY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createColorPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld ), + xAnimateNode ); + + case AnimationFactory::CLASS_STRING_PROPERTY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createStringPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, 0 ), + xAnimateNode ); + + case AnimationFactory::CLASS_BOOL_PROPERTY: + return ActivitiesFactory::createAnimateActivity( + aParms, + AnimationFactory::createBoolPropertyAnimation( + attrName, + pShape, + getContext().mpSubsettableShapeManager, + getSlideSize(), + getContext().mpBox2DWorld, 0 ), + xAnimateNode ); + } + + return AnimationActivitySharedPtr(); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/propertyanimationnode.hxx b/slideshow/source/engine/animationnodes/propertyanimationnode.hxx new file mode 100644 index 000000000..015a00413 --- /dev/null +++ b/slideshow/source/engine/animationnodes/propertyanimationnode.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX + +#include "animationbasenode.hxx" + +namespace slideshow::internal { + +class PropertyAnimationNode : public AnimationBaseNode +{ +public: + PropertyAnimationNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + ::std::shared_ptr<BaseContainerNode> const& pParent, + NodeContext const& rContext ) + : AnimationBaseNode( xNode, pParent, rContext ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override + { return "PropertyAnimationNode"; } +#endif + +private: + virtual AnimationActivitySharedPtr createActivity() const override; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx b/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx new file mode 100644 index 000000000..3f928593c --- /dev/null +++ b/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <delayevent.hxx> +#include <eventqueue.hxx> +#include <usereventqueue.hxx> +#include "sequentialtimecontainer.hxx" + +namespace slideshow::internal { + +void SequentialTimeContainer::activate_st() +{ + // resolve first possible child, ignore + for ( ; mnFinishedChildren < maChildren.size(); ++mnFinishedChildren ) { + if (resolveChild( maChildren[mnFinishedChildren] )) + break; + else { + // node still UNRESOLVED, no need to deactivate or end... + OSL_FAIL( "### resolving child failed!" ); + } + } + + if (isDurationIndefinite() && + (maChildren.empty() || mnFinishedChildren >= maChildren.size())) + { + // deactivate ASAP: + auto self(getSelf()); + scheduleDeactivationEvent( + makeEvent( [self] () { self->deactivate(); }, + "SequentialTimeContainer::deactivate") ); + } + else // use default + scheduleDeactivationEvent(); +} + +void SequentialTimeContainer::dispose() +{ + BaseContainerNode::dispose(); + if (mpCurrentSkipEvent) { + mpCurrentSkipEvent->dispose(); + mpCurrentSkipEvent.reset(); + } +} + +void SequentialTimeContainer::skipEffect( + AnimationNodeSharedPtr const& pChildNode ) +{ + if (isChildNode(pChildNode)) { + // empty all events ignoring timings => until next effect + getContext().mrEventQueue.forceEmpty(); + getContext().mrEventQueue.addEvent( + makeEvent( [pChildNode] () { pChildNode->deactivate(); }, + "SequentialTimeContainer::deactivate, skipEffect with delay") ); + } + else + OSL_FAIL( "unknown notifier!" ); +} + +bool SequentialTimeContainer::resolveChild( + AnimationNodeSharedPtr const& pChildNode ) +{ + bool const bResolved = pChildNode->resolve(); + if (bResolved && isMainSequenceRootNode()) { + // discharge events: + if (mpCurrentSkipEvent) + mpCurrentSkipEvent->dispose(); + + // event that will deactivate the resolved/running child: + mpCurrentSkipEvent = makeEvent( + std::bind( &SequentialTimeContainer::skipEffect, + std::dynamic_pointer_cast<SequentialTimeContainer>( getSelf() ), + pChildNode ), + "SequentialTimeContainer::skipEffect, resolveChild"); + + // deactivate child node when skip event occurs: + getContext().mrUserEventQueue.registerSkipEffectEvent( + mpCurrentSkipEvent, + mnFinishedChildren+1<maChildren.size()); + } + return bResolved; +} + +void SequentialTimeContainer::notifyDeactivating( + AnimationNodeSharedPtr const& rNotifier ) +{ + if (notifyDeactivatedChild( rNotifier )) + return; + + OSL_ASSERT( mnFinishedChildren < maChildren.size() ); + AnimationNodeSharedPtr const& pNextChild = maChildren[mnFinishedChildren]; + OSL_ASSERT( pNextChild->getState() == UNRESOLVED ); + + if (! resolveChild( pNextChild )) { + // could not resolve child - since we risk to + // stall the chain of events here, play it safe + // and deactivate this node (only if we have + // indefinite duration - otherwise, we'll get a + // deactivation event, anyways). + deactivate(); + } +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx b/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx new file mode 100644 index 000000000..bc277c951 --- /dev/null +++ b/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX + +#include <basecontainernode.hxx> + +namespace slideshow::internal { + +/** This class implements sequential node containers + + All children of this node are played sequentially +*/ +class SequentialTimeContainer : public BaseContainerNode +{ +public: + SequentialTimeContainer( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + BaseContainerNodeSharedPtr const& pParent, + NodeContext const& rContext ) + : BaseContainerNode( xNode, pParent, rContext ) {} + +#if defined(DBG_UTIL) + virtual const char* getDescription() const override + { return "SequentialTimeContainer"; } +#endif + +protected: + virtual void dispose() override; + +private: + virtual void activate_st() override; + virtual void notifyDeactivating( AnimationNodeSharedPtr const& rNotifier ) override; + + void skipEffect( AnimationNodeSharedPtr const& pChildNode ); + +private: + bool resolveChild( AnimationNodeSharedPtr const& pChildNode ); + + EventSharedPtr mpCurrentSkipEvent; +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationnodes/setactivity.hxx b/slideshow/source/engine/animationnodes/setactivity.hxx new file mode 100644 index 000000000..b3585af2e --- /dev/null +++ b/slideshow/source/engine/animationnodes/setactivity.hxx @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX + +#include <tools/diagnose_ex.h> + +#include <animationactivity.hxx> +#include <animatableshape.hxx> +#include <shapeattributelayer.hxx> +#include <activitiesfactory.hxx> + +namespace slideshow::internal { + +/** Templated setter for animation values + + This template class implements the AnimationActivity + interface, but only the perform() and + setAttributeLayer() methods are functional. To be used for set animations. + + @see AnimationSetNode. +*/ +template <class AnimationT> +class SetActivity : public AnimationActivity +{ +public: + typedef ::std::shared_ptr< AnimationT > AnimationSharedPtrT; + typedef typename AnimationT::ValueType ValueT; + + SetActivity( const ActivitiesFactory::CommonParameters& rParms, + const AnimationSharedPtrT& rAnimation, + const ValueT& rToValue ) + : mpAnimation( rAnimation ), + mpShape(), + mpAttributeLayer(), + mpEndEvent( rParms.mpEndEvent ), + mrEventQueue( rParms.mrEventQueue ), + maToValue( rToValue ), + mbIsActive(true) + { + ENSURE_OR_THROW( mpAnimation, "Invalid animation" ); + } + + virtual void dispose() override + { + mbIsActive = false; + mpAnimation.reset(); + mpShape.reset(); + mpAttributeLayer.reset(); + // discharge end event: + if (mpEndEvent && mpEndEvent->isCharged()) + mpEndEvent->dispose(); + mpEndEvent.reset(); + } + + virtual double calcTimeLag() const override + { + return 0.0; + } + + virtual bool perform() override + { + if (! isActive()) + return false; + // we're going inactive immediately: + mbIsActive = false; + + if (mpAnimation && mpAttributeLayer && mpShape) { + mpAnimation->start( mpShape, mpAttributeLayer ); + (*mpAnimation)(maToValue); + mpAnimation->end(); + } + // fire end event, if any + if (mpEndEvent) + mrEventQueue.addEvent( mpEndEvent ); + + return false; // don't reinsert + } + + virtual bool isActive() const override + { + return mbIsActive; + } + + virtual void dequeued() override + { + } + + virtual void end() override + { + perform(); + } + + virtual void setTargets( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override + { + ENSURE_OR_THROW( rShape, "Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, "Invalid attribute layer" ); + + mpShape = rShape; + mpAttributeLayer = rAttrLayer; + } + +private: + AnimationSharedPtrT mpAnimation; + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttributeLayer; + EventSharedPtr mpEndEvent; + EventQueue& mrEventQueue; + ValueT maToValue; + bool mbIsActive; +}; + +template <class AnimationT> AnimationActivitySharedPtr makeSetActivity( + const ActivitiesFactory::CommonParameters& rParms, + const ::std::shared_ptr< AnimationT >& rAnimation, + const typename AnimationT::ValueType& rToValue ) +{ + return std::make_shared<SetActivity<AnimationT>>(rParms,rAnimation,rToValue); +} + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |