diff options
Diffstat (limited to '')
15 files changed, 2844 insertions, 0 deletions
diff --git a/slideshow/source/engine/activities/accumulation.hxx b/slideshow/source/engine/activities/accumulation.hxx new file mode 100644 index 000000000..ec0419559 --- /dev/null +++ b/slideshow/source/engine/activities/accumulation.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACCUMULATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACCUMULATION_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> + + +namespace slideshow::internal + { + /** Generic accumulation. + + This template handles value accumulation across repeated + effect runs: returned is the end value times the repeat + count, plus the current value. + + @param rEndValue + End value of the simple animation. + + @param nRepeatCount + Number of completed repeats (i.e. 0 during the first + effect run) + + @param rCurrValue + Current animation value + */ + template< typename ValueType > ValueType accumulate( const ValueType& rEndValue, + sal_uInt32 nRepeatCount, + const ValueType& rCurrValue ) + { + return nRepeatCount*rEndValue + rCurrValue; + } + + /// Specialization for non-addable enums/constant values + template<> sal_Int16 accumulate< sal_Int16 >( const sal_Int16&, + sal_uInt32, + const sal_Int16& rCurrValue ) + { + // always return rCurrValue, it's forbidden to add enums/constant values... + return rCurrValue; + } + + /// Specialization for non-addable strings + template<> OUString accumulate< OUString >( const OUString&, + sal_uInt32, + const OUString& rCurrValue ) + { + // always return rCurrValue, it's impossible to add strings... + return rCurrValue; + } + + /// Specialization for non-addable bools + template<> bool accumulate< bool >( const bool&, + sal_uInt32, + const bool& bCurrValue ) + { + // always return bCurrValue, SMIL spec requires to ignore + // cumulative behaviour for bools. + return bCurrValue; + } + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACCUMULATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/activitiesfactory.cxx b/slideshow/source/engine/activities/activitiesfactory.cxx new file mode 100644 index 000000000..2dadfea49 --- /dev/null +++ b/slideshow/source/engine/activities/activitiesfactory.cxx @@ -0,0 +1,1015 @@ +/* -*- 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/animations/AnimationCalcMode.hpp> +#include <comphelper/sequence.hxx> + +#include <activitiesfactory.hxx> +#include <slideshowexceptions.hxx> +#include <smilfunctionparser.hxx> +#include "accumulation.hxx" +#include "activityparameters.hxx" +#include "interpolation.hxx" +#include <tools.hxx> +#include "simplecontinuousactivitybase.hxx" +#include "discreteactivitybase.hxx" +#include "continuousactivitybase.hxx" +#include "continuouskeytimeactivitybase.hxx" + +#include <optional> + +#include <memory> +#include <vector> +#include <algorithm> + +using namespace com::sun::star; + +namespace slideshow::internal { + +namespace { + +/** Traits template, to take formula application only for ValueType = double + */ +template<typename ValueType> struct FormulaTraits +{ + static ValueType getPresentationValue( + const ValueType& rVal, const std::shared_ptr<ExpressionNode>& ) + { + return rVal; + } +}; + +/// Specialization for ValueType = double +template<> struct FormulaTraits<double> +{ + static double getPresentationValue( + double const& rVal, std::shared_ptr<ExpressionNode> const& rFormula ) + { + return rFormula ? (*rFormula)(rVal) : rVal; + } +}; + +// Various ActivityBase specializations for different animator types +// ================================================================= + +/** FromToBy handler + + Provides the Activity specializations for FromToBy + animations (e.g. those without a values list). + + This template makes heavy use of SFINAE, only one of + the perform*() methods will compile for each of the + base classes. + + Note that we omit the virtual keyword on the perform() + overrides on purpose; those that actually do override + baseclass virtual methods inherit the property, and + the others won't increase our vtable. What's more, + having all perform() method in the vtable actually + creates POIs for them, which breaks the whole SFINAE + concept (IOW, this template won't compile any longer). + + @tpl BaseType + Base class to use for this activity. Only + ContinuousActivityBase and DiscreteActivityBase are + supported here. + + @tpl AnimationType + Type of the Animation to call. +*/ +template<class BaseType, typename AnimationType> +class FromToByActivity : public BaseType +{ +public: + typedef typename AnimationType::ValueType ValueType; + typedef std::optional<ValueType> OptionalValueType; + +private: + // some compilers don't inline whose definition they haven't + // seen before the call site... + ValueType getPresentationValue( const ValueType& rVal ) const + { + return FormulaTraits<ValueType>::getPresentationValue( rVal, mpFormula); + } + +public: + /** Create FromToByActivity. + + @param rFrom + From this value, the animation starts + + @param rTo + With this value, the animation ends + + @param rBy + With this value, the animation increments the start value + + @param rParms + Standard Activity parameter struct + + @param rAnim + Shared ptr to AnimationType + + @param rInterpolator + Interpolator object to be used for lerping between + start and end value (need to be passed, since it + might contain state, e.g. interpolation direction + for HSL color space). + + @param bCumulative + Whether repeated animations should cumulate the + value, or start fresh each time. + */ + FromToByActivity( + const OptionalValueType& rFrom, + const OptionalValueType& rTo, + const OptionalValueType& rBy, + const ActivityParameters& rParms, + const ::std::shared_ptr< AnimationType >& rAnim, + const Interpolator< ValueType >& rInterpolator, + bool bCumulative ) + : BaseType( rParms ), + maFrom( rFrom ), + maTo( rTo ), + maBy( rBy ), + mpFormula( rParms.mpFormula ), + maStartValue(), + maEndValue(), + maPreviousValue(), + maStartInterpolationValue(), + mnIteration( 0 ), + mpAnim( rAnim ), + maInterpolator( rInterpolator ), + mbDynamicStartValue( false ), + mbCumulative( bCumulative ) + { + ENSURE_OR_THROW( mpAnim, "Invalid animation object" ); + + ENSURE_OR_THROW( + rTo || rBy, + "From and one of To or By, or To or By alone must be valid" ); + } + + virtual void startAnimation() + { + if (this->isDisposed() || !mpAnim) + return; + BaseType::startAnimation(); + + // start animation + mpAnim->start( BaseType::getShape(), + BaseType::getShapeAttributeLayer() ); + + // setup start and end value. Determine animation + // start value only when animation actually + // started up (this order is part of the Animation + // interface contract) + const ValueType aAnimationStartValue( mpAnim->getUnderlyingValue() ); + + // first of all, determine general type of + // animation, by inspecting which of the FromToBy values + // are actually valid. + // See http://www.w3.org/TR/smil20/animation.html#AnimationNS-FromToBy + // for a definition + if( maFrom ) + { + // From-to or From-by animation. According to + // SMIL spec, the To value takes precedence + // over the By value, if both are specified + if( maTo ) + { + // From-To animation + maStartValue = *maFrom; + maEndValue = *maTo; + } + else if( maBy ) + { + // From-By animation + maStartValue = *maFrom; + maEndValue = maStartValue + *maBy; + } + maStartInterpolationValue = maStartValue; + } + else + { + maStartValue = aAnimationStartValue; + maStartInterpolationValue = maStartValue; + + // By or To animation. According to SMIL spec, + // the To value takes precedence over the By + // value, if both are specified + if( maTo ) + { + // To animation + + // According to the SMIL spec + // (http://www.w3.org/TR/smil20/animation.html#animationNS-ToAnimation), + // the to animation interpolates between + // the _running_ underlying value and the to value (as the end value) + mbDynamicStartValue = true; + maPreviousValue = maStartValue; + maEndValue = *maTo; + } + else if( maBy ) + { + // By animation + maStartValue = aAnimationStartValue; + maEndValue = maStartValue + *maBy; + } + } + } + + virtual void endAnimation() + { + // end animation + if (mpAnim) + mpAnim->end(); + } + + /// perform override for ContinuousActivityBase + void perform( double nModifiedTime, sal_uInt32 nRepeatCount ) const + { + if (this->isDisposed() || !mpAnim) + return; + + // According to SMIL 3.0 spec 'to' animation if no other (lower priority) + // animations are active or frozen then a simple interpolation is performed. + // That is, the start interpolation value is constant while the animation + // is running, and is equal to the underlying value retrieved when + // the animation start. + // However if another animation is manipulating the underlying value, + // the 'to' animation will initially add to the effect of the lower priority + // animation, and increasingly dominate it as it nears the end of the + // simple duration, eventually overriding it completely. + // That is, each time the underlying value is changed between two + // computations of the animation function the new underlying value is used + // as start value for the interpolation. + // See: + // http://www.w3.org/TR/SMIL3/smil-animation.html#animationNS-ToAnimation + // (Figure 6 - Effect of Additive to animation example) + // Moreover when a 'to' animation is repeated, at each new iteration + // the start interpolation value is reset to the underlying value + // of the animated property when the animation started, + // as it is shown in the example provided by the SMIL 3.0 spec. + // This is exactly as Firefox performs SVG 'to' animations. + if( mbDynamicStartValue ) + { + if( mnIteration != nRepeatCount ) + { + mnIteration = nRepeatCount; + maStartInterpolationValue = maStartValue; + } + else + { + ValueType aActualValue = mpAnim->getUnderlyingValue(); + if( aActualValue != maPreviousValue ) + maStartInterpolationValue = aActualValue; + } + } + + ValueType aValue = maInterpolator( maStartInterpolationValue, + maEndValue, nModifiedTime ); + + // According to the SMIL spec: + // Because 'to' animation is defined in terms of absolute values of + // the target attribute, cumulative animation is not defined. + if( mbCumulative && !mbDynamicStartValue ) + { + // aValue = this.aEndValue * nRepeatCount + aValue; + aValue = accumulate( maEndValue, nRepeatCount, aValue ); + } + + (*mpAnim)( getPresentationValue( aValue ) ); + + if( mbDynamicStartValue ) + { + maPreviousValue = mpAnim->getUnderlyingValue(); + } + + } + + using BaseType::perform; + + /// perform override for DiscreteActivityBase base + void perform( sal_uInt32 nFrame, sal_uInt32 nRepeatCount ) const + { + if (this->isDisposed() || !mpAnim) + return; + (*mpAnim)( + getPresentationValue( + accumulate( maEndValue, mbCumulative ? nRepeatCount : 0, + lerp( maInterpolator, + (mbDynamicStartValue + ? mpAnim->getUnderlyingValue() + : maStartValue), + maEndValue, + nFrame, + BaseType::getNumberOfKeyTimes() ) ) ) ); + } + + using BaseType::isAutoReverse; + + virtual void performEnd() + { + // xxx todo: good guess + if (mpAnim) + { + if (isAutoReverse()) + (*mpAnim)( getPresentationValue( maStartValue ) ); + else + (*mpAnim)( getPresentationValue( maEndValue ) ); + } + } + + /// Disposable: + virtual void dispose() + { + mpAnim.reset(); + BaseType::dispose(); + } + +private: + const OptionalValueType maFrom; + const OptionalValueType maTo; + const OptionalValueType maBy; + + std::shared_ptr<ExpressionNode> mpFormula; + + ValueType maStartValue; + ValueType maEndValue; + + mutable ValueType maPreviousValue; + mutable ValueType maStartInterpolationValue; + mutable sal_uInt32 mnIteration; + + ::std::shared_ptr< AnimationType > mpAnim; + Interpolator< ValueType > maInterpolator; + bool mbDynamicStartValue; + bool mbCumulative; +}; + + +/** Generate Activity corresponding to given FromToBy values + + @tpl BaseType + BaseType to use for deriving the Activity from + + @tpl AnimationType + Subtype of the Animation object (e.g. NumberAnimation) +*/ +template<class BaseType, typename AnimationType> +AnimationActivitySharedPtr createFromToByActivity( + const uno::Any& rFromAny, + const uno::Any& rToAny, + const uno::Any& rByAny, + const ActivityParameters& rParms, + const ::std::shared_ptr< AnimationType >& rAnim, + const Interpolator< typename AnimationType::ValueType >& rInterpolator, + bool bCumulative, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) +{ + typedef typename AnimationType::ValueType ValueType; + typedef std::optional<ValueType> OptionalValueType; + + OptionalValueType aFrom; + OptionalValueType aTo; + OptionalValueType aBy; + + ValueType aTmpValue; + + if( rFromAny.hasValue() ) + { + ENSURE_OR_THROW( + extractValue( aTmpValue, rFromAny, rShape, rSlideBounds ), + "createFromToByActivity(): Could not extract from value" ); + aFrom = aTmpValue; + } + if( rToAny.hasValue() ) + { + ENSURE_OR_THROW( + extractValue( aTmpValue, rToAny, rShape, rSlideBounds ), + "createFromToByActivity(): Could not extract to value" ); + aTo = aTmpValue; + } + if( rByAny.hasValue() ) + { + ENSURE_OR_THROW( + extractValue( aTmpValue, rByAny, rShape, rSlideBounds ), + "createFromToByActivity(): Could not extract by value" ); + aBy = aTmpValue; + } + + return std::make_shared<FromToByActivity<BaseType, AnimationType>>( + aFrom, + aTo, + aBy, + rParms, + rAnim, + rInterpolator, + bCumulative ); +} + +/* The following table shows which animator combines with + which Activity type: + + NumberAnimator: all + PairAnimation: all + ColorAnimation: all + StringAnimation: DiscreteActivityBase + BoolAnimation: DiscreteActivityBase +*/ + +/** Values handler + + Provides the Activity specializations for value lists + animations. + + This template makes heavy use of SFINAE, only one of + the perform*() methods will compile for each of the + base classes. + + Note that we omit the virtual keyword on the perform() + overrides on purpose; those that actually do override + baseclass virtual methods inherit the property, and + the others won't increase our vtable. What's more, + having all perform() method in the vtable actually + creates POIs for them, which breaks the whole SFINAE + concept (IOW, this template won't compile any longer). + + @tpl BaseType + Base class to use for this activity. Only + ContinuousKeyTimeActivityBase and DiscreteActivityBase + are supported here. For values animation without key + times, the client must emulate key times by providing + a vector of equally spaced values between 0 and 1, + with the same number of entries as the values vector. + + @tpl AnimationType + Type of the Animation to call. +*/ +template<class BaseType, typename AnimationType> +class ValuesActivity : public BaseType +{ +public: + typedef typename AnimationType::ValueType ValueType; + typedef std::vector<ValueType> ValueVectorType; + +private: + // some compilers don't inline methods whose definition they haven't + // seen before the call site... + ValueType getPresentationValue( const ValueType& rVal ) const + { + return FormulaTraits<ValueType>::getPresentationValue( + rVal, mpFormula ); + } + +public: + /** Create ValuesActivity. + + @param rValues + Value vector to cycle animation through + + @param rParms + Standard Activity parameter struct + + @param rAnim + Shared ptr to AnimationType + + @param rInterpolator + Interpolator object to be used for lerping between + start and end value (need to be passed, since it + might contain state, e.g. interpolation direction + for HSL color space). + + @param bCumulative + Whether repeated animations should cumulate the + value, or start afresh each time. + */ + ValuesActivity( + const ValueVectorType& rValues, + const ActivityParameters& rParms, + const std::shared_ptr<AnimationType>& rAnim, + const Interpolator< ValueType >& rInterpolator, + bool bCumulative ) + : BaseType( rParms ), + maValues( rValues ), + mpFormula( rParms.mpFormula ), + mpAnim( rAnim ), + maInterpolator( rInterpolator ), + mbCumulative( bCumulative ) + { + ENSURE_OR_THROW( mpAnim, "Invalid animation object" ); + ENSURE_OR_THROW( !rValues.empty(), "Empty value vector" ); + } + + virtual void startAnimation() + { + if (this->isDisposed() || !mpAnim) + return; + BaseType::startAnimation(); + + // start animation + mpAnim->start( BaseType::getShape(), + BaseType::getShapeAttributeLayer() ); + } + + virtual void endAnimation() + { + // end animation + if (mpAnim) + mpAnim->end(); + } + + /// perform override for ContinuousKeyTimeActivityBase base + void perform( sal_uInt32 nIndex, + double nFractionalIndex, + sal_uInt32 nRepeatCount ) const + { + if (this->isDisposed() || !mpAnim) + return; + ENSURE_OR_THROW( nIndex+1 < maValues.size(), + "ValuesActivity::perform(): index out of range" ); + + // interpolate between nIndex and nIndex+1 values + (*mpAnim)( + getPresentationValue( + accumulate<ValueType>( maValues.back(), + mbCumulative ? nRepeatCount : 0, + maInterpolator( maValues[ nIndex ], + maValues[ nIndex+1 ], + nFractionalIndex ) ) ) ); + } + + using BaseType::perform; + + /// perform override for DiscreteActivityBase base + void perform( sal_uInt32 nFrame, sal_uInt32 nRepeatCount ) const + { + if (this->isDisposed() || !mpAnim) + return; + ENSURE_OR_THROW( nFrame < maValues.size(), + "ValuesActivity::perform(): index out of range" ); + + // this is discrete, thus no lerp here. + (*mpAnim)( + getPresentationValue( + slideshow::internal::accumulate<ValueType>( maValues.back(), + mbCumulative ? nRepeatCount : 0, + maValues[ nFrame ] ) ) ); + } + + virtual void performEnd() + { + // xxx todo: good guess + if (mpAnim) + (*mpAnim)( getPresentationValue( maValues.back() ) ); + } + +private: + ValueVectorType maValues; + + std::shared_ptr<ExpressionNode> mpFormula; + + std::shared_ptr<AnimationType> mpAnim; + Interpolator< ValueType > maInterpolator; + bool mbCumulative; +}; + +/** Generate Activity corresponding to given Value vector + + @tpl BaseType + BaseType to use for deriving the Activity from + + @tpl AnimationType + Subtype of the Animation object (e.g. NumberAnimation) +*/ +template<class BaseType, typename AnimationType> +AnimationActivitySharedPtr createValueListActivity( + const uno::Sequence<uno::Any>& rValues, + const ActivityParameters& rParms, + const std::shared_ptr<AnimationType>& rAnim, + const Interpolator<typename AnimationType::ValueType>& rInterpolator, + bool bCumulative, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) +{ + typedef typename AnimationType::ValueType ValueType; + typedef std::vector<ValueType> ValueVectorType; + + ValueVectorType aValueVector; + aValueVector.reserve( rValues.getLength() ); + + for( const auto& rValue : rValues ) + { + ValueType aValue; + ENSURE_OR_THROW( + extractValue( aValue, rValue, rShape, rSlideBounds ), + "createValueListActivity(): Could not extract values" ); + aValueVector.push_back( aValue ); + } + + return std::make_shared<ValuesActivity<BaseType, AnimationType>>( + aValueVector, + rParms, + rAnim, + rInterpolator, + bCumulative ); +} + +/** Generate Activity for given XAnimate, corresponding to given Value vector + + @tpl AnimationType + Subtype of the Animation object (e.g. NumberAnimation) + + @param rParms + Common activity parameters + + @param xNode + XAnimate node, to retrieve animation values from + + @param rAnim + Actual animation to operate with (gets called with the + time-dependent values) + + @param rInterpolator + Interpolator object to be used for lerping between + start and end values (need to be passed, since it + might contain state, e.g. interpolation direction + for HSL color space). +*/ +template<typename AnimationType> +AnimationActivitySharedPtr createActivity( + const ActivitiesFactory::CommonParameters& rParms, + const uno::Reference< animations::XAnimate >& xNode, + const ::std::shared_ptr< AnimationType >& rAnim, + const Interpolator< typename AnimationType::ValueType >& rInterpolator + = Interpolator< typename AnimationType::ValueType >() ) +{ + // setup common parameters + // ======================= + + ActivityParameters aActivityParms( rParms.mpEndEvent, + rParms.mrEventQueue, + rParms.mrActivitiesQueue, + rParms.mnMinDuration, + rParms.maRepeats, + rParms.mnAcceleration, + rParms.mnDeceleration, + rParms.mnMinNumberOfFrames, + rParms.mbAutoReverse ); + + // is a formula given? + const OUString& rFormulaString( xNode->getFormula() ); + if( !rFormulaString.isEmpty() ) + { + // yep, parse and pass to ActivityParameters + try + { + aActivityParms.mpFormula = + SmilFunctionParser::parseSmilFunction( + rFormulaString, + calcRelativeShapeBounds( + rParms.maSlideBounds, + rParms.mpShape->getBounds() ) ); + } + catch( ParseError& ) + { + // parse error, thus no formula + OSL_FAIL( "createActivity(): Error parsing formula string" ); + } + } + + // are key times given? + const uno::Sequence< double >& aKeyTimes( xNode->getKeyTimes() ); + if( aKeyTimes.hasElements() ) + { + // yes, convert them from Sequence< double > + aActivityParms.maDiscreteTimes.resize( aKeyTimes.getLength() ); + comphelper::sequenceToArray( + aActivityParms.maDiscreteTimes.data(), + aKeyTimes ); // saves us some temporary vectors + } + + // values sequence given? + const sal_Int32 nValueLen( xNode->getValues().getLength() ); + if( nValueLen ) + { + // Value list activity + // =================== + + // fake keytimes, if necessary + if( !aKeyTimes.hasElements() ) + { + // create a dummy vector of key times, + // with aValues.getLength equally spaced entries. + for( sal_Int32 i=0; i<nValueLen; ++i ) + aActivityParms.maDiscreteTimes.push_back( double(i)/nValueLen ); + } + + // determine type of animation needed here: + // Value list activities are possible with + // ContinuousKeyTimeActivityBase and DiscreteActivityBase + // specializations + const sal_Int16 nCalcMode( xNode->getCalcMode() ); + + switch( nCalcMode ) + { + case animations::AnimationCalcMode::DISCRETE: + { + // since DiscreteActivityBase suspends itself + // between the frames, create a WakeupEvent for it. + aActivityParms.mpWakeupEvent = + std::make_shared<WakeupEvent>( + rParms.mrEventQueue.getTimer(), + rParms.mrActivitiesQueue ); + + AnimationActivitySharedPtr pActivity( + createValueListActivity< DiscreteActivityBase >( + xNode->getValues(), + aActivityParms, + rAnim, + rInterpolator, + xNode->getAccumulate(), + rParms.mpShape, + rParms.maSlideBounds ) ); + + // WakeupEvent and DiscreteActivityBase need circular + // references to the corresponding other object. + aActivityParms.mpWakeupEvent->setActivity( pActivity ); + + return pActivity; + } + + default: + OSL_FAIL( "createActivity(): unexpected case" ); + [[fallthrough]]; + case animations::AnimationCalcMode::PACED: + case animations::AnimationCalcMode::SPLINE: + case animations::AnimationCalcMode::LINEAR: + return createValueListActivity< ContinuousKeyTimeActivityBase >( + xNode->getValues(), + aActivityParms, + rAnim, + rInterpolator, + xNode->getAccumulate(), + rParms.mpShape, + rParms.maSlideBounds ); + } + } + else + { + // FromToBy activity + // ================= + + // determine type of animation needed here: + // FromToBy activities are possible with + // ContinuousActivityBase and DiscreteActivityBase + // specializations + const sal_Int16 nCalcMode( xNode->getCalcMode() ); + + switch( nCalcMode ) + { + case animations::AnimationCalcMode::DISCRETE: + { + // fake keytimes, if necessary + if( !aKeyTimes.hasElements() ) + { + // create a dummy vector of 2 key times + const ::std::size_t nLen( 2 ); + for( ::std::size_t i=0; i<nLen; ++i ) + aActivityParms.maDiscreteTimes.push_back( double(i)/nLen ); + } + + // since DiscreteActivityBase suspends itself + // between the frames, create a WakeupEvent for it. + aActivityParms.mpWakeupEvent = + std::make_shared<WakeupEvent>( + rParms.mrEventQueue.getTimer(), + rParms.mrActivitiesQueue ); + + AnimationActivitySharedPtr pActivity( + createFromToByActivity< DiscreteActivityBase >( + xNode->getFrom(), + xNode->getTo(), + xNode->getBy(), + aActivityParms, + rAnim, + rInterpolator, + xNode->getAccumulate(), + rParms.mpShape, + rParms.maSlideBounds ) ); + + // WakeupEvent and DiscreteActivityBase need circular + // references to the corresponding other object. + aActivityParms.mpWakeupEvent->setActivity( pActivity ); + + return pActivity; + } + + default: + OSL_FAIL( "createActivity(): unexpected case" ); + [[fallthrough]]; + case animations::AnimationCalcMode::PACED: + case animations::AnimationCalcMode::SPLINE: + case animations::AnimationCalcMode::LINEAR: + return createFromToByActivity< ContinuousActivityBase >( + xNode->getFrom(), + xNode->getTo(), + xNode->getBy(), + aActivityParms, + rAnim, + rInterpolator, + xNode->getAccumulate(), + rParms.mpShape, + rParms.maSlideBounds ); + } + } +} + +/** Simple activity for ActivitiesFactory::createSimpleActivity + + @tpl Direction + Determines direction of value generator. A 1 yields a + forward direction, starting with 0.0 and ending with + 1.0. A 0 yields a backward direction, starting with + 1.0 and ending with 0.0 +*/ +template<int Direction> +class SimpleActivity : public ContinuousActivityBase +{ +public: + /** Create SimpleActivity. + + @param rParms + Standard Activity parameter struct + */ + SimpleActivity( const ActivityParameters& rParms, + const NumberAnimationSharedPtr& rAnim ) : + ContinuousActivityBase( rParms ), + mpAnim( rAnim ) + { + ENSURE_OR_THROW( mpAnim, "Invalid animation object" ); + } + + virtual void startAnimation() override + { + if (this->isDisposed() || !mpAnim) + return; + ContinuousActivityBase::startAnimation(); + + // start animation + mpAnim->start( getShape(), + getShapeAttributeLayer() ); + } + + virtual void endAnimation() override + { + // end animation + if (mpAnim) + mpAnim->end(); + } + + using SimpleContinuousActivityBase::perform; + + /// perform override for ContinuousActivityBase + virtual void perform( double nModifiedTime, sal_uInt32 ) const override + { + if (this->isDisposed() || !mpAnim) + return; + // no cumulation, simple [0,1] range + (*mpAnim)( 1.0 - Direction + nModifiedTime*(2.0*Direction - 1.0) ); + } + + virtual void performEnd() override + { + // xxx todo: review + if (mpAnim) + (*mpAnim)( 1.0*Direction ); + } + + /// Disposable: + virtual void dispose() override + { + mpAnim.reset(); + ContinuousActivityBase::dispose(); + } + +private: + NumberAnimationSharedPtr mpAnim; +}; + +} // anon namespace + + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const NumberAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const EnumAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const ColorAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const HSLColorAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimateColor >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, + uno::Reference< animations::XAnimate >( + xNode, uno::UNO_QUERY_THROW ), + rAnim, + // Direction==true means clockwise in SMIL API + Interpolator< HSLColor >( !xNode->getDirection() ) ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const PairAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const StringAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createAnimateActivity( + const CommonParameters& rParms, + const BoolAnimationSharedPtr& rAnim, + const uno::Reference< animations::XAnimate >& xNode ) +{ + // forward to appropriate template instantiation + return createActivity( rParms, xNode, rAnim ); +} + +AnimationActivitySharedPtr ActivitiesFactory::createSimpleActivity( + const CommonParameters& rParms, + const NumberAnimationSharedPtr& rAnim, + bool bDirectionForward ) +{ + ActivityParameters aActivityParms( rParms.mpEndEvent, + rParms.mrEventQueue, + rParms.mrActivitiesQueue, + rParms.mnMinDuration, + rParms.maRepeats, + rParms.mnAcceleration, + rParms.mnDeceleration, + rParms.mnMinNumberOfFrames, + rParms.mbAutoReverse ); + + if( bDirectionForward ) + return std::make_shared<SimpleActivity<1>>( aActivityParms, rAnim ); + else + return std::make_shared<SimpleActivity<0>>( aActivityParms, rAnim ); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/activitybase.cxx b/slideshow/source/engine/activities/activitybase.cxx new file mode 100644 index 000000000..82a117f8a --- /dev/null +++ b/slideshow/source/engine/activities/activitybase.cxx @@ -0,0 +1,234 @@ +/* -*- 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 <algorithm> + +#include <tools/diagnose_ex.h> + +#include "activitybase.hxx" + + +namespace slideshow::internal +{ + // TODO(P1): Elide some virtual function calls, by templifying this + // static hierarchy + + ActivityBase::ActivityBase( const ActivityParameters& rParms ) : + mpEndEvent( rParms.mrEndEvent ), + mrEventQueue( rParms.mrEventQueue ), + mpShape(), + mpAttributeLayer(), + maRepeats( rParms.mrRepeats ), + mnAccelerationFraction( rParms.mnAccelerationFraction ), + mnDecelerationFraction( rParms.mnDecelerationFraction ), + mbAutoReverse( rParms.mbAutoReverse ), + mbFirstPerformCall( true ), + mbIsActive( true ) {} + + void ActivityBase::dispose() + { + // deactivate + mbIsActive = false; + + // dispose event + if( mpEndEvent ) + mpEndEvent->dispose(); + + // release references + mpEndEvent.reset(); + mpShape.reset(); + mpAttributeLayer.reset(); + } + + double ActivityBase::calcTimeLag() const + { + // TODO(Q1): implement different init process! + if (isActive() && mbFirstPerformCall) + { + mbFirstPerformCall = false; + + // notify derived classes that we're + // starting now + const_cast<ActivityBase *>(this)->startAnimation(); + } + return 0.0; + } + + bool ActivityBase::perform() + { + // still active? + if( !isActive() ) + return false; // no, early exit. + + OSL_ASSERT( ! mbFirstPerformCall ); + + return true; + } + + bool ActivityBase::isActive() const + { + return mbIsActive; + } + + void ActivityBase::setTargets( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) + { + ENSURE_OR_THROW( rShape, + "ActivityBase::setTargets(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "ActivityBase::setTargets(): Invalid attribute layer" ); + + mpShape = rShape; + mpAttributeLayer = rAttrLayer; + } + + void ActivityBase::endActivity() + { + // this is a regular activity end + mbIsActive = false; + + // Activity is ending, queue event, then + if( mpEndEvent ) + mrEventQueue.addEvent( mpEndEvent ); + + // release references + mpEndEvent.reset(); + } + + void ActivityBase::dequeued() + { + // xxx todo: +// // ignored here, if we're still active. Discrete +// // activities are dequeued after every perform() call, +// // thus, the call is only significant when isActive() == +// // false. + if( !isActive() ) + endAnimation(); + } + + void ActivityBase::end() + { + if (!isActive() || isDisposed()) + return; + // assure animation is started: + if (mbFirstPerformCall) { + mbFirstPerformCall = false; + // notify derived classes that we're starting now + startAnimation(); + } + + performEnd(); // calling private virtual + endAnimation(); + endActivity(); + } + + double ActivityBase::calcAcceleratedTime( double nT ) const + { + // Handle acceleration/deceleration + // ================================ + + // clamp nT to permissible [0,1] range + nT = std::clamp( nT, 0.0, 1.0 ); + + // take acceleration/deceleration into account. if the sum + // of mnAccelerationFraction and mnDecelerationFraction + // exceeds 1.0, ignore both (that's according to SMIL spec) + if( (mnAccelerationFraction > 0.0 || + mnDecelerationFraction > 0.0) && + mnAccelerationFraction + mnDecelerationFraction <= 1.0 ) + { + /* + // calc accelerated/decelerated time. + + // We have three intervals: + // 1 [0,a] + // 2 [a,d] + // 3 [d,1] (with a and d being acceleration/deceleration + // fraction, resp.) + + // The change rate during interval 1 is constantly + // increasing, reaching 1 at a. It then stays at 1, + // starting a linear decrease at d, ending with 0 at + // time 1. The integral of this function is the + // required new time nT'. + + // As we arbitrarily assumed 1 as the upper value of + // the change rate, the integral must be normalized to + // reach nT'=1 at the end of the interval. This + // normalization constant is: + + // c = 1 - 0.5a - 0.5d + + // The integral itself then amounts to: + + // 0.5 nT^2 / a + (nT-a) + (nT - 0.5 nT^2 / d) + + // (where each of the three summands correspond to the + // three intervals above, and are applied only if nT + // has reached the corresponding interval) + + // The graph of the change rate is a trapezoid: + + // | + // 1| /--------------\ + // | / \ + // | / \ + // | / \ + // ----------------------------- + // 0 a d 1 + + //*/ + const double nC( 1.0 - 0.5*mnAccelerationFraction - 0.5*mnDecelerationFraction ); + + // this variable accumulates the new time value + double nTPrime(0.0); + + if( nT < mnAccelerationFraction ) + { + nTPrime += 0.5*nT*nT/mnAccelerationFraction; // partial first interval + } + else + { + nTPrime += 0.5*mnAccelerationFraction; // full first interval + + if( nT <= 1.0-mnDecelerationFraction ) + { + nTPrime += nT-mnAccelerationFraction; // partial second interval + } + else + { + nTPrime += 1.0 - mnAccelerationFraction - mnDecelerationFraction; // full second interval + + const double nTRelative( nT - 1.0 + mnDecelerationFraction ); + + nTPrime += nTRelative - 0.5*nTRelative*nTRelative / mnDecelerationFraction; + } + } + + // normalize, and assign to work variable + nT = nTPrime / nC; + } + + return nT; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/activitybase.hxx b/slideshow/source/engine/activities/activitybase.hxx new file mode 100644 index 000000000..e76c84d8e --- /dev/null +++ b/slideshow/source/engine/activities/activitybase.hxx @@ -0,0 +1,143 @@ +/* -*- 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_ACTIVITIES_ACTIVITYBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACTIVITYBASE_HXX + +#include <animationactivity.hxx> +#include "activityparameters.hxx" +#include <animatableshape.hxx> +#include <shapeattributelayer.hxx> + +namespace slideshow::internal { + +/** Base class for animation activities. + + This whole class hierarchy is only for code sharing + between the various specializations (with or without + key times, fully discrete, etc.). +*/ +class ActivityBase : public AnimationActivity +{ +public: + explicit ActivityBase( const ActivityParameters& rParms ); + + /// From Disposable interface + virtual void dispose() override; + +protected: + /** From Activity interface + + Derived classes should override, call this first + and then perform their work. + */ + virtual bool perform() override; + virtual double calcTimeLag() const override; + virtual bool isActive() const override; + +private: + virtual void dequeued() override; + + // From AnimationActivity interface + virtual void setTargets( + const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override; + +private: + /** Hook for derived classes + + This method will be called from the first + perform() invocation, to signal the start of the + activity. + */ + virtual void startAnimation() = 0; + + /** Hook for derived classes + + This method will be called after the last perform() + invocation, and after the potential changes of that + perform() call are committed to screen. That is, in + endAnimation(), the animation objects (sprites, + animation) can safely be destroyed, without causing + visible artifacts on screen. + */ + virtual void endAnimation() = 0; + +protected: + + /** End this activity, in a regular way. + + This method is for derived classes needing to signal a + regular activity end (i.e. because the regular + duration is over) + */ + void endActivity(); + + /** Modify fractional time. + + This method modifies the fractional time (total + duration mapped to the [0,1] range) to the + effective simple time, but only according to + acceleration/deceleration. + */ + double calcAcceleratedTime( double nT ) const; + + bool isDisposed() const { + return (!mbIsActive && !mpEndEvent && !mpShape && + !mpAttributeLayer); + } + + EventQueue& getEventQueue() const { return mrEventQueue; } + + const AnimatableShapeSharedPtr& getShape() const { return mpShape; } + + const ShapeAttributeLayerSharedPtr& getShapeAttributeLayer() const + { return mpAttributeLayer; } + + bool isRepeatCountValid() const { return bool(maRepeats); } + double getRepeatCount() const { return *maRepeats; } + bool isAutoReverse() const { return mbAutoReverse; } + +private: + /// Activity: + virtual void end() override; + virtual void performEnd() = 0; + +private: + EventSharedPtr mpEndEvent; + EventQueue& mrEventQueue; + AnimatableShapeSharedPtr mpShape; // only to pass on to animation + ShapeAttributeLayerSharedPtr mpAttributeLayer; // only to pass on to anim + + ::std::optional<double> const maRepeats; + const double mnAccelerationFraction; + const double mnDecelerationFraction; + + const bool mbAutoReverse; + + // true, if perform() has not yet been called: + mutable bool mbFirstPerformCall; + bool mbIsActive; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACTIVITYBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/activityparameters.hxx b/slideshow/source/engine/activities/activityparameters.hxx new file mode 100644 index 000000000..9df762838 --- /dev/null +++ b/slideshow/source/engine/activities/activityparameters.hxx @@ -0,0 +1,134 @@ +/* -*- 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_ACTIVITIES_ACTIVITYPARAMETERS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACTIVITYPARAMETERS_HXX + +#include <event.hxx> +#include <eventqueue.hxx> +#include <expressionnode.hxx> +#include <wakeupevent.hxx> + +#include <optional> +#include <vector> + +namespace slideshow::internal { + +/** Parameter struct for animation activities + + This struct contains all common parameters needed to + initialize the activities generated by the ActivityFactory. +*/ +struct ActivityParameters +{ + /** Create + + @param rEndEvent + Event to be fired, when the activity ends. + + @param rEventQueue + Queue to add end event to + + @param nMinDuration + Minimal duration of the activity (might actually be + longer because of nMinNumberOfFrames). Note that this + duration must always be the <em>simple</em> duration, + i.e. without any repeat. + + @param rRepeats + Number of repeats. If this parameter is invalid, + infinite repeat is assumed. + + @param nAccelerationFraction + Value between 0 and 1, denoting the fraction of the + total simple duration, which the animation should + accelerate. + + @param nDecelerationFraction + Value between 0 and 1, denoting the fraction of the + total simple duration, which the animation should + decelerate. Note that the ranges + [0,nAccelerationFraction] and + [nDecelerationFraction,1] must be non-overlapping! + + @param bAutoReverse + When true, at the end of the simple duration, the + animation plays reversed to the start value. Note that + nMinDuration still specifies the simple duration, + i.e. when bAutoReverse is true, the implicit duration + doubles. + */ + ActivityParameters( + const EventSharedPtr& rEndEvent, + EventQueue& rEventQueue, + ActivitiesQueue& rActivitiesQueue, + double nMinDuration, + ::std::optional<double> const& rRepeats, + double nAccelerationFraction, + double nDecelerationFraction, + sal_uInt32 nMinNumberOfFrames, + bool bAutoReverse ) + : mrEndEvent( rEndEvent ), + mpWakeupEvent(), + mrEventQueue( rEventQueue ), + mrActivitiesQueue( rActivitiesQueue ), + mpFormula(), + maDiscreteTimes(), + mnMinDuration( nMinDuration ), + mrRepeats( rRepeats ), + mnAccelerationFraction( nAccelerationFraction ), + mnDecelerationFraction( nDecelerationFraction ), + mnMinNumberOfFrames( nMinNumberOfFrames ), + mbAutoReverse( bAutoReverse ) {} + + /// End event to fire, when activity is over + const EventSharedPtr& mrEndEvent; + /// Wakeup event to use for discrete activities + WakeupEventSharedPtr mpWakeupEvent; + + /// EventQueue to add events to + EventQueue& mrEventQueue; + + /// ActivitiesQueue to add events to + ActivitiesQueue& mrActivitiesQueue; + + /// Optional formula + std::shared_ptr<ExpressionNode> mpFormula; + + /// Key times, for discrete and key time activities + ::std::vector< double > maDiscreteTimes; + + /// Total duration of activity (including all repeats) + const double mnMinDuration; + ::std::optional<double> const& mrRepeats; + const double mnAccelerationFraction; + const double mnDecelerationFraction; + + /// Minimal number of frames this activity must render + const sal_uInt32 mnMinNumberOfFrames; + + /// When true, activity is played reversed after mnDuration. + const bool mbAutoReverse; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_ACTIVITYPARAMETERS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/continuousactivitybase.cxx b/slideshow/source/engine/activities/continuousactivitybase.cxx new file mode 100644 index 000000000..64e2377f9 --- /dev/null +++ b/slideshow/source/engine/activities/continuousactivitybase.cxx @@ -0,0 +1,39 @@ +/* -*- 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 "continuousactivitybase.hxx" + + +namespace slideshow::internal +{ + ContinuousActivityBase::ContinuousActivityBase( const ActivityParameters& rParms ) : + SimpleContinuousActivityBase( rParms ) + { + } + + void ContinuousActivityBase::simplePerform( double nSimpleTime, + sal_uInt32 nRepeatCount ) const + { + perform( calcAcceleratedTime( nSimpleTime ), + nRepeatCount ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/continuousactivitybase.hxx b/slideshow/source/engine/activities/continuousactivitybase.hxx new file mode 100644 index 000000000..8fd19d421 --- /dev/null +++ b/slideshow/source/engine/activities/continuousactivitybase.hxx @@ -0,0 +1,65 @@ +/* -*- 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_ACTIVITIES_CONTINUOUSACTIVITYBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_CONTINUOUSACTIVITYBASE_HXX + +#include "simplecontinuousactivitybase.hxx" + +namespace slideshow::internal + { + /** Simple, continuous animation. + + This class implements a simple, continuous + animation. Only addition to ActivityBase class is an + explicit animation duration and a minimal number of + frames to display. + */ + class ContinuousActivityBase : public SimpleContinuousActivityBase + { + public: + explicit ContinuousActivityBase( const ActivityParameters& rParms ); + + using SimpleContinuousActivityBase::perform; + + /** Hook for derived classes + + This method will be called from perform(), already + equipped with the modified time (nMinNumberOfFrames, repeat, + acceleration and deceleration taken into account). + + @param nModifiedTime + Already accelerated/decelerated and repeated time, always + in the [0,1] range. + + @param nRepeatCount + Number of full repeats already performed + */ + virtual void perform( double nModifiedTime, sal_uInt32 nRepeatCount ) const = 0; + + /// From SimpleContinuousActivityBase class + virtual void simplePerform( double nSimpleTime, + sal_uInt32 nRepeatCount ) const override; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_CONTINUOUSACTIVITYBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/continuouskeytimeactivitybase.cxx b/slideshow/source/engine/activities/continuouskeytimeactivitybase.cxx new file mode 100644 index 000000000..db25d2dd4 --- /dev/null +++ b/slideshow/source/engine/activities/continuouskeytimeactivitybase.cxx @@ -0,0 +1,59 @@ +/* -*- 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 "continuouskeytimeactivitybase.hxx" + +#include <tuple> + +namespace slideshow::internal +{ + ContinuousKeyTimeActivityBase::ContinuousKeyTimeActivityBase( const ActivityParameters& rParms ) : + SimpleContinuousActivityBase( rParms ), + maLerper( std::vector(rParms.maDiscreteTimes) ) + { + ENSURE_OR_THROW( rParms.maDiscreteTimes.size() > 1, + "ContinuousKeyTimeActivityBase::ContinuousKeyTimeActivityBase(): key times vector must have two entries or more" ); + ENSURE_OR_THROW( rParms.maDiscreteTimes.front() == 0.0, + "ContinuousKeyTimeActivityBase::ContinuousKeyTimeActivityBase(): key times vector first entry must be zero" ); + ENSURE_OR_THROW( rParms.maDiscreteTimes.back() <= 1.0, + "ContinuousKeyTimeActivityBase::ContinuousKeyTimeActivityBase(): key times vector last entry must be less or equal 1" ); + } + + void ContinuousKeyTimeActivityBase::simplePerform( double nSimpleTime, + sal_uInt32 nRepeatCount ) const + { + // calc simple time from global time - sweep through the + // array multiple times for repeated animations (according to + // SMIL spec). + double fAlpha( calcAcceleratedTime( nSimpleTime ) ); + std::ptrdiff_t nIndex; + + std::tie(nIndex,fAlpha) = maLerper.lerp(fAlpha); + + perform( + nIndex, + fAlpha, + nRepeatCount ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/continuouskeytimeactivitybase.hxx b/slideshow/source/engine/activities/continuouskeytimeactivitybase.hxx new file mode 100644 index 000000000..2238db228 --- /dev/null +++ b/slideshow/source/engine/activities/continuouskeytimeactivitybase.hxx @@ -0,0 +1,74 @@ +/* -*- 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_ACTIVITIES_CONTINUOUSKEYTIMEACTIVITYBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_CONTINUOUSKEYTIMEACTIVITYBASE_HXX + +#include "simplecontinuousactivitybase.hxx" + +#include <basegfx/utils/keystoplerp.hxx> + + +namespace slideshow::internal + { + /** Interpolated, key-times animation. + + This class implements an interpolated key-times + animation, with continuous time. + */ + class ContinuousKeyTimeActivityBase : public SimpleContinuousActivityBase + { + public: + explicit ContinuousKeyTimeActivityBase( const ActivityParameters& rParms ); + + using SimpleContinuousActivityBase::perform; + + /** Hook for derived classes + + This method will be called from perform(), already + equipped with the modified time (nMinNumberOfFrames, repeat, + acceleration and deceleration taken into account). + + @param nIndex + Current index of the key times/key values. + + @param nFractionalIndex + Fractional value from the [0,1] range, specifying + the position between nIndex and nIndex+1. + + @param nRepeatCount + Number of full repeats already performed + */ + virtual void perform( sal_uInt32 nIndex, + double nFractionalIndex, + sal_uInt32 nRepeatCount ) const = 0; + + /// From SimpleContinuousActivityBase class + virtual void simplePerform( double nSimpleTime, + sal_uInt32 nRepeatCount ) const override; + + private: + const ::basegfx::utils::KeyStopLerp maLerper; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_CONTINUOUSKEYTIMEACTIVITYBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/discreteactivitybase.cxx b/slideshow/source/engine/activities/discreteactivitybase.cxx new file mode 100644 index 000000000..2fe0935b6 --- /dev/null +++ b/slideshow/source/engine/activities/discreteactivitybase.cxx @@ -0,0 +1,192 @@ +/* -*- 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 "discreteactivitybase.hxx" + + +namespace slideshow::internal +{ + DiscreteActivityBase::DiscreteActivityBase( const ActivityParameters& rParms ) : + ActivityBase( rParms ), + mpWakeupEvent( rParms.mpWakeupEvent ), + maDiscreteTimes( rParms.maDiscreteTimes ), + mnSimpleDuration( rParms.mnMinDuration ), + mnCurrPerformCalls( 0 ) + { + ENSURE_OR_THROW( mpWakeupEvent, + "DiscreteActivityBase::DiscreteActivityBase(): Invalid wakeup event" ); + + ENSURE_OR_THROW( !maDiscreteTimes.empty(), + "DiscreteActivityBase::DiscreteActivityBase(): time vector is empty, why do you create me?" ); + +#ifdef DBG_UTIL + // check parameters: rDiscreteTimes must be sorted in + // ascending order, and contain values only from the range + // [0,1] + for( ::std::size_t i=1, len=maDiscreteTimes.size(); i<len; ++i ) + { + if( maDiscreteTimes[i] < 0.0 || + maDiscreteTimes[i] > 1.0 || + maDiscreteTimes[i-1] < 0.0 || + maDiscreteTimes[i-1] > 1.0 ) + { + ENSURE_OR_THROW( false, "DiscreteActivityBase::DiscreteActivityBase(): time values not within [0,1] range!" ); + } + + if( maDiscreteTimes[i-1] > maDiscreteTimes[i] ) + ENSURE_OR_THROW( false, "DiscreteActivityBase::DiscreteActivityBase(): time vector is not sorted in ascending order!" ); + } + + // TODO(E2): check this also in production code? +#endif + } + + void DiscreteActivityBase::startAnimation() + { + // start timer on wakeup event + mpWakeupEvent->start(); + } + + sal_uInt32 DiscreteActivityBase::calcFrameIndex( sal_uInt32 nCurrCalls, + ::std::size_t nVectorSize ) const + { + if( isAutoReverse() ) + { + // every full repeat run consists of one + // forward and one backward traversal. + sal_uInt32 nFrameIndex( nCurrCalls % (2*nVectorSize) ); + + // nFrameIndex values >= nVectorSize belong to + // the backward traversal + if( nFrameIndex >= nVectorSize ) + nFrameIndex = 2*nVectorSize - nFrameIndex; // invert sweep + + return nFrameIndex; + } + else + { + return nCurrCalls % nVectorSize ; + } + } + + sal_uInt32 DiscreteActivityBase::calcRepeatCount( sal_uInt32 nCurrCalls, + ::std::size_t nVectorSize ) const + { + if( isAutoReverse() ) + return nCurrCalls / (2*nVectorSize); // we've got 2 cycles per repeat + else + return nCurrCalls / nVectorSize; + } + + bool DiscreteActivityBase::perform() + { + // call base class, for start() calls and end handling + if( !ActivityBase::perform() ) + return false; // done, we're ended + + const ::std::size_t nVectorSize( maDiscreteTimes.size() ); + + // actually perform something + // ========================== + + // TODO(Q3): Refactor this mess + + // call derived class with current frame index (modulo + // vector size, to cope with repeats) + perform( calcFrameIndex( mnCurrPerformCalls, nVectorSize ), + calcRepeatCount( mnCurrPerformCalls, nVectorSize ) ); + + // calc next index + ++mnCurrPerformCalls; + + // calc currently reached repeat count + double nCurrRepeat( double(mnCurrPerformCalls) / nVectorSize ); + + // if auto-reverse is specified, halve the + // effective repeat count, since we pass every + // repeat run twice: once forward, once backward. + if( isAutoReverse() ) + nCurrRepeat /= 2.0; + + // schedule next frame, if either repeat is indefinite + // (repeat forever), or we've not yet reached the requested + // repeat count + if( !isRepeatCountValid() || + nCurrRepeat < getRepeatCount() ) + { + // add wake-up event to queue (modulo + // vector size, to cope with repeats). + + // repeat is handled locally, only apply acceleration/deceleration. + // Scale time vector with simple duration, offset with full repeat + // times. + + // Somewhat condensed, the argument for setNextTimeout below could + // be written as + + // mnSimpleDuration*(nFullRepeats + calcAcceleratedTime( currentRepeatTime )), + + // with currentRepeatTime = maDiscreteTimes[ currentRepeatIndex ] + + // Note that calcAcceleratedTime() is only applied to the current repeat's value, + // not to the total resulting time. This is in accordance with the SMIL spec. + + mpWakeupEvent->setNextTimeout( + mnSimpleDuration*( + calcRepeatCount( + mnCurrPerformCalls, + nVectorSize ) + + calcAcceleratedTime( + maDiscreteTimes[ + calcFrameIndex( + mnCurrPerformCalls, + nVectorSize ) ] ) ) ); + + getEventQueue().addEvent( mpWakeupEvent ); + } + else + { + // release event reference (relation to wakeup event + // is circular!) + mpWakeupEvent.reset(); + + // done with this activity + endActivity(); + } + + return false; // remove from queue, will be added back by the wakeup event. + } + + void DiscreteActivityBase::dispose() + { + // dispose event + if( mpWakeupEvent ) + mpWakeupEvent->dispose(); + + // release references + mpWakeupEvent.reset(); + + ActivityBase::dispose(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/discreteactivitybase.hxx b/slideshow/source/engine/activities/discreteactivitybase.hxx new file mode 100644 index 000000000..1332ad3c5 --- /dev/null +++ b/slideshow/source/engine/activities/discreteactivitybase.hxx @@ -0,0 +1,79 @@ +/* -*- 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_ACTIVITIES_DISCRETEACTIVITYBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_DISCRETEACTIVITYBASE_HXX + +#include "activitybase.hxx" +#include <wakeupevent.hxx> + +#include <vector> + + +namespace slideshow::internal + { + /** Specialization of ActivityBase for discrete time activities. + + A discrete time activity is one where time proceeds in + discrete steps, i.e. at given time instants. + */ + class DiscreteActivityBase : public ActivityBase + { + public: + explicit DiscreteActivityBase( const ActivityParameters& rParms ); + + /** Hook for derived classes. + + This method is called for each discrete time + instant, with nFrame denoting the frame number + (starting with 0) + + @param nFrame + Current frame number. + + @param nRepeatCount + Number of full repeats already performed + */ + virtual void perform( sal_uInt32 nFrame, sal_uInt32 nRepeatCount ) const = 0; + virtual void dispose() override; + virtual bool perform() override; + + protected: + virtual void startAnimation() override; + + sal_uInt32 calcFrameIndex( sal_uInt32 nCurrCalls, + ::std::size_t nVectorSize ) const; + + sal_uInt32 calcRepeatCount( sal_uInt32 nCurrCalls, + ::std::size_t nVectorSize ) const; + + ::std::size_t getNumberOfKeyTimes() const { return maDiscreteTimes.size(); } + + private: + WakeupEventSharedPtr mpWakeupEvent; + const ::std::vector< double > maDiscreteTimes; + const double mnSimpleDuration; + sal_uInt32 mnCurrPerformCalls; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_DISCRETEACTIVITYBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/interpolation.hxx b/slideshow/source/engine/activities/interpolation.hxx new file mode 100644 index 000000000..155456ed5 --- /dev/null +++ b/slideshow/source/engine/activities/interpolation.hxx @@ -0,0 +1,190 @@ +/* -*- 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_ACTIVITIES_INTERPOLATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_INTERPOLATION_HXX + +#include <basegfx/utils/lerp.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> + +#include <rgbcolor.hxx> +#include <hslcolor.hxx> + +namespace basegfx +{ + namespace utils + { + // Interpolator specializations + // ============================ + + // NOTE: generic lerp is included from lerp.hxx. Following + // are some specializations for various + // not-straight-forward-interpolatable types + + /// Specialization for RGBColor, to employ color-specific interpolator + template<> ::slideshow::internal::RGBColor lerp< ::slideshow::internal::RGBColor >( + const ::slideshow::internal::RGBColor& rFrom, + const ::slideshow::internal::RGBColor& rTo, + double t ) + { + return interpolate( rFrom, rTo, t ); + } + + /// Specialization also for sal_Int16, although this code should not be called + template<> sal_Int16 lerp< sal_Int16 >( const sal_Int16&, + const sal_Int16& rTo, + double ) + { + OSL_FAIL( "lerp<sal_Int16> called" ); + return rTo; + } + + /// Specialization also for string, although this code should not be called + template<> OUString lerp< OUString >( const OUString&, + const OUString& rTo, + double ) + { + OSL_FAIL( "lerp<OUString> called" ); + return rTo; + } + + /// Specialization also for bool, although this code should not be called + template<> bool lerp< bool >( const bool&, + const bool& rTo, + double ) + { + OSL_FAIL( "lerp<bool> called" ); + return rTo; + } + } +} + +namespace slideshow +{ + namespace internal + { + template< typename ValueType > struct Interpolator + { + ValueType operator()( const ValueType& rFrom, + const ValueType& rTo, + double t ) const + { + return basegfx::utils::lerp( rFrom, rTo, t ); + } + }; + + /// Specialization for HSLColor, to employ color-specific interpolator + template<> struct Interpolator< HSLColor > + { + explicit Interpolator( bool bCCW ) : + mbCCW( bCCW ) + { + } + + HSLColor operator()( const HSLColor& rFrom, + const HSLColor& rTo, + double t ) const + { + return interpolate( rFrom, rTo, t, mbCCW ); + } + + private: + /// When true: interpolate counter-clockwise + const bool mbCCW; + }; + + + /** Generic linear interpolator + + @tpl ValueType + Must have operator+ and operator* defined, and should + have value semantics. + + @param rInterpolator + Interpolator to use for lerp + + @param nFrame + Must be in the [0,nTotalFrames) range + + @param nTotalFrames + Total number of frames. Should be greater than zero. + */ + template< typename ValueType > ValueType lerp( const Interpolator< ValueType >& rInterpolator, + const ValueType& rFrom, + const ValueType& rTo, + sal_uInt32 nFrame, + ::std::size_t nTotalFrames ) + { + // TODO(P1): There's a nice HAKMEM trick for that + // nTotalFrames > 1 condition below + + // for 1 and 0 frame animations, always take end value + const double nFraction( nTotalFrames > 1 ? double(nFrame)/(nTotalFrames-1) : 1.0 ); + + return rInterpolator( rFrom, rTo, nFraction ); + } + + /// Specialization for non-interpolatable constants/enums + template<> sal_Int16 lerp< sal_Int16 >( const Interpolator< sal_Int16 >& /*rInterpolator*/, + const sal_Int16& rFrom, + const sal_Int16& rTo, + sal_uInt32 nFrame, + ::std::size_t nTotalFrames ) + { + // until one half of the total frames are over, take from value. + // after that, take to value. + // For nFrames not divisible by 2, we prefer to over from, which + // also neatly yields to for 1 frame activities + return nFrame < nTotalFrames/2 ? rFrom : rTo; + } + + /// Specialization for non-interpolatable strings + template<> OUString lerp< OUString >( const Interpolator< OUString >& /*rInterpolator*/, + const OUString& rFrom, + const OUString& rTo, + sal_uInt32 nFrame, + ::std::size_t nTotalFrames ) + { + // until one half of the total frames are over, take from value. + // after that, take to value. + // For nFrames not divisible by 2, we prefer to over from, which + // also neatly yields to for 1 frame activities + return nFrame < nTotalFrames/2 ? rFrom : rTo; + } + + /// Specialization for non-interpolatable bools + template<> bool lerp< bool >( const Interpolator< bool >& /*rInterpolator*/, + const bool& bFrom, + const bool& bTo, + sal_uInt32 nFrame, + ::std::size_t nTotalFrames ) + { + // until one half of the total frames are over, take from value. + // after that, take to value. + // For nFrames not divisible by 2, we prefer to over from, which + // also neatly yields to for 1 frame activities + return nFrame < nTotalFrames/2 ? bFrom : bTo; + } + } +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_INTERPOLATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/simplecontinuousactivitybase.cxx b/slideshow/source/engine/activities/simplecontinuousactivitybase.cxx new file mode 100644 index 000000000..01cb3b750 --- /dev/null +++ b/slideshow/source/engine/activities/simplecontinuousactivitybase.cxx @@ -0,0 +1,253 @@ +/* -*- 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 . + */ + + +// must be first + +#include "simplecontinuousactivitybase.hxx" + +#include <sal/log.hxx> + +namespace slideshow::internal +{ + SimpleContinuousActivityBase::SimpleContinuousActivityBase( + const ActivityParameters& rParms ) : + ActivityBase( rParms ), + maTimer( rParms.mrActivitiesQueue.getTimer() ), + mnMinSimpleDuration( rParms.mnMinDuration ), + mnMinNumberOfFrames( rParms.mnMinNumberOfFrames ), + mnCurrPerformCalls( 0 ) + { + } + + void SimpleContinuousActivityBase::startAnimation() + { + // init timer. We measure animation time only when we're + // actually started. + maTimer.reset(); + } + + double SimpleContinuousActivityBase::calcTimeLag() const + { + ActivityBase::calcTimeLag(); + if (! isActive()) + return 0.0; + + // retrieve locally elapsed time + const double nCurrElapsedTime( maTimer.getElapsedTime() ); + + // log time + SAL_INFO("slideshow.verbose", "SimpleContinuousActivityBase::calcTimeLag(): " + "next step is based on time: " << nCurrElapsedTime ); + + // go to great length to ensure a proper animation + // run. Since we don't know how often we will be called + // here, try to spread the animator calls uniquely over + // the [0,1] parameter range. Be aware of the fact that + // perform will be called at least mnMinNumberOfTurns + // times. + + // fraction of time elapsed (clamp to 1.0 for zero-length + // animations) + const double nFractionElapsedTime( + mnMinSimpleDuration != 0.0 ? + nCurrElapsedTime / mnMinSimpleDuration : + 1.0 ); + + // fraction of minimum calls performed + const double nFractionRequiredCalls( + double(mnCurrPerformCalls) / mnMinNumberOfFrames ); + + // okay, so now, the decision is easy: + + // If the fraction of time elapsed is smaller than the + // number of calls required to be performed, then we calc + // the position on the animation range according to + // elapsed time. That is, we're so to say ahead of time. + + // In contrary, if the fraction of time elapsed is larger, + // then we're lagging, and we thus calc the position on + // the animation time line according to the fraction of + // calls performed. Thus, the animation is forced to slow + // down, and take the required minimal number of steps, + // sufficiently equally distributed across the animation + // time line. + if( nFractionElapsedTime < nFractionRequiredCalls ) + { + SAL_INFO("slideshow.verbose", "SimpleContinuousActivityBase::calcTimeLag(): t=" << + nFractionElapsedTime << + " is based on time"); + return 0.0; + } + else + { + SAL_INFO("slideshow.verbose", "SimpleContinuousActivityBase::perform(): t=" << + nFractionRequiredCalls << + " is based on number of calls"); + + // lag global time, so all other animations lag, too: + return ((nFractionElapsedTime - nFractionRequiredCalls) + * mnMinSimpleDuration); + } + } + + bool SimpleContinuousActivityBase::perform() + { + // call base class, for start() calls and end handling + if( !ActivityBase::perform() ) + return false; // done, we're ended + + + // get relative animation position + // =============================== + + const double nCurrElapsedTime( maTimer.getElapsedTime() ); + // clamp to 1.0 for zero animation duration + double nT( mnMinSimpleDuration != 0.0 ? + nCurrElapsedTime / mnMinSimpleDuration : + 1.0 ); + + + // one of the stop criteria reached? + // ================================= + + // will be set to true below, if one of the termination criteria + // matched. + bool bActivityEnding( false ); + + if( isRepeatCountValid() ) + { + // Finite duration + // =============== + + // When we've autoreverse on, the repeat count + // doubles + const double nRepeatCount( getRepeatCount() ); + const double nEffectiveRepeat( isAutoReverse() ? + 2.0*nRepeatCount : + nRepeatCount ); + + // time (or frame count) elapsed? + if( nEffectiveRepeat <= nT ) + { + // okee. done for now. Will not exit right here, + // to give animation the chance to render the last + // frame below + bActivityEnding = true; + + // clamp animation to max permissible value + nT = nEffectiveRepeat; + } + } + + + // need to do auto-reverse? + // ======================== + + double nRepeats; + double nRelativeSimpleTime; + + // TODO(Q3): Refactor this mess + if( isAutoReverse() ) + { + // divert active duration into repeat and + // fractional part. + const double nFractionalActiveDuration( modf(nT, &nRepeats) ); + + // for auto-reverse, map ranges [1,2), [3,4), ... + // to ranges [0,1), [1,2), etc. + if( static_cast<int>(nRepeats) % 2 ) + { + // we're in an odd range, reverse sweep + nRelativeSimpleTime = 1.0 - nFractionalActiveDuration; + } + else + { + // we're in an even range, pass on as is + nRelativeSimpleTime = nFractionalActiveDuration; + } + + // effective repeat count for autoreverse is half of + // the input time's value (each run of an autoreverse + // cycle is half of a repeat) + nRepeats /= 2; + } + else + { + // determine repeat + // ================ + + // calc simple time and number of repeats from nT + // Now, that's easy, since the fractional part of + // nT gives the relative simple time, and the + // integer part the number of full repeats: + nRelativeSimpleTime = modf(nT, &nRepeats); + + // clamp repeats to max permissible value (maRepeats.getValue() - 1.0) + if( isRepeatCountValid() && + nRepeats >= getRepeatCount() ) + { + // Note that this code here only gets + // triggered if maRepeats.getValue() is an + // _integer_. Otherwise, nRepeats will never + // reach nor exceed + // maRepeats.getValue(). Thus, the code below + // does not need to handle cases of fractional + // repeats, and can always assume that a full + // animation run has ended (with + // nRelativeSimpleTime=1.0 for + // non-autoreversed activities). + + // with modf, nRelativeSimpleTime will never + // become 1.0, since nRepeats is incremented and + // nRelativeSimpleTime set to 0.0 then. + + // For the animation to reach its final value, + // nRepeats must although become + // maRepeats.getValue()-1.0, and + // nRelativeSimpleTime=1.0. + nRelativeSimpleTime = 1.0; + nRepeats -= 1.0; + } + } + + // actually perform something + // ========================== + + simplePerform( nRelativeSimpleTime, + // nRepeats is already integer-valued + static_cast<sal_uInt32>( nRepeats ) ); + + + // delayed endActivity() call from end condition check + // below. Issued after the simplePerform() call above, to + // give animations the chance to correctly reach the + // animation end value, without spurious bail-outs because + // of isActive() returning false. + if( bActivityEnding ) + endActivity(); + + // one more frame successfully performed + ++mnCurrPerformCalls; + + return isActive(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activities/simplecontinuousactivitybase.hxx b/slideshow/source/engine/activities/simplecontinuousactivitybase.hxx new file mode 100644 index 000000000..fb4f74605 --- /dev/null +++ b/slideshow/source/engine/activities/simplecontinuousactivitybase.hxx @@ -0,0 +1,79 @@ +/* -*- 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_ACTIVITIES_SIMPLECONTINUOUSACTIVITYBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_SIMPLECONTINUOUSACTIVITYBASE_HXX + +#include "activitybase.hxx" +#include <canvas/elapsedtime.hxx> + +namespace slideshow::internal + { + /** Simple, continuous animation. + + This class implements a simple, continuous animation + without considering repeats or acceleration on the + perform call. Only useful as a base class, you + probably want to use ContinuousActivityBase. + */ + class SimpleContinuousActivityBase : public ActivityBase + { + public: + explicit SimpleContinuousActivityBase( const ActivityParameters& rParms ); + + virtual double calcTimeLag() const override; + virtual bool perform() override; + + protected: + /** Hook for derived classes + + This method will be called from perform(). + + @param nSimpleTime + Simple animation time, without repeat, + acceleration or deceleration applied. This value + is always in the [0,1] range, the repeat is + accounted for with the nRepeatCount parameter. + + @param nRepeatCount + Number of full repeats already performed + */ + virtual void simplePerform( double nSimpleTime, sal_uInt32 nRepeatCount ) const = 0; + + virtual void startAnimation() override; + + private: + /// Time elapsed since activity started + ::canvas::tools::ElapsedTime maTimer; + + /// Simple duration of activity + const double mnMinSimpleDuration; + + /// Minimal number of frames to show (see ActivityParameters) + const sal_uInt32 mnMinNumberOfFrames; + + /// Actual number of frames shown until now. + sal_uInt32 mnCurrPerformCalls; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ACTIVITIES_SIMPLECONTINUOUSACTIVITYBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/activitiesqueue.cxx b/slideshow/source/engine/activitiesqueue.cxx new file mode 100644 index 000000000..2e3b29d9d --- /dev/null +++ b/slideshow/source/engine/activitiesqueue.cxx @@ -0,0 +1,204 @@ +/* -*- 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 <osl/diagnose.h> +#include <sal/log.hxx> + +#include <slideshowexceptions.hxx> +#include <activity.hxx> +#include <activitiesqueue.hxx> + +#include <algorithm> +#include <memory> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + ActivitiesQueue::ActivitiesQueue( + const std::shared_ptr< ::canvas::tools::ElapsedTime >& pPresTimer ) : + mpTimer( pPresTimer ), + maCurrentActivitiesWaiting(), + maCurrentActivitiesReinsert(), + maDequeuedActivities() + { + } + + ActivitiesQueue::~ActivitiesQueue() + { + // dispose all queue entries + try + { + for( const auto& pActivity : maCurrentActivitiesWaiting ) + pActivity->dispose(); + for( const auto& pActivity : maCurrentTailActivitiesWaiting ) + pActivity->dispose(); + for( const auto& pActivity : maCurrentActivitiesReinsert ) + pActivity->dispose(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + } + + bool ActivitiesQueue::addActivity( const ActivitySharedPtr& pActivity ) + { + OSL_ENSURE( pActivity, "ActivitiesQueue::addActivity: activity ptr NULL" ); + + if( !pActivity ) + return false; + + // add entry to waiting list + maCurrentActivitiesWaiting.push_back( pActivity ); + + return true; + } + + bool ActivitiesQueue::addTailActivity(const ActivitySharedPtr &pActivity) + { + OSL_ENSURE( pActivity, "ActivitiesQueue::addTailActivity: activity ptr NULL" ); + + if( !pActivity ) + return false; + + // Activities that should be processed last are kept in a different + // ActivityQueue, and later appended to the end of the maCurrentActivitiesWaiting + // on the beginning of ActivitiesQueue::process() + maCurrentTailActivitiesWaiting.push_back( pActivity ); + + return true; + } + + void ActivitiesQueue::process() + { + SAL_INFO("slideshow.verbose", "ActivitiesQueue: outer loop heartbeat" ); + + // If there are activities to be processed last append them to the end of the ActivitiesQueue + maCurrentActivitiesWaiting.insert( maCurrentActivitiesWaiting.end(), + maCurrentTailActivitiesWaiting.begin(), + maCurrentTailActivitiesWaiting.end() ); + maCurrentTailActivitiesWaiting.clear(); + + // accumulate time lag for all activities, and lag time + // base if necessary: + double fLag = 0.0; + for ( const auto& rxActivity : maCurrentActivitiesWaiting ) + fLag = std::max<double>( fLag, rxActivity->calcTimeLag() ); + if (fLag > 0.0) + { + mpTimer->adjustTimer( -fLag ); + } + + // process list of activities + while( !maCurrentActivitiesWaiting.empty() ) + { + // process topmost activity + ActivitySharedPtr pActivity( maCurrentActivitiesWaiting.front() ); + maCurrentActivitiesWaiting.pop_front(); + + bool bReinsert( false ); + + try + { + // fire up activity + bReinsert = pActivity->perform(); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + // catch anything here, we don't want + // to leave this scope under _any_ + // circumstance. Although, do _not_ + // reinsert an activity that threw + // once. + + // NOTE: we explicitly don't catch(...) here, + // since this will also capture segmentation + // violations and the like. In such a case, we + // still better let our clients now... + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + catch( SlideShowException& ) + { + // catch anything here, we don't want + // to leave this scope under _any_ + // circumstance. Although, do _not_ + // reinsert an activity that threw + // once. + + // NOTE: we explicitly don't catch(...) here, + // since this will also capture segmentation + // violations and the like. In such a case, we + // still better let our clients now... + SAL_WARN("slideshow", "::presentation::internal::ActivitiesQueue: Activity threw a SlideShowException, removing from ring" ); + } + + if( bReinsert ) + maCurrentActivitiesReinsert.push_back( pActivity ); + else + maDequeuedActivities.push_back( pActivity ); + + SAL_INFO("slideshow.verbose", "ActivitiesQueue: inner loop heartbeat" ); + } + + if( !maCurrentActivitiesReinsert.empty() ) + { + // reinsert all processed, but not finished + // activities back to waiting queue. With swap(), + // we kill two birds with one stone: we reuse the + // list nodes, and we clear the + // maCurrentActivitiesReinsert list + maCurrentActivitiesWaiting.swap( maCurrentActivitiesReinsert ); + } + } + + void ActivitiesQueue::processDequeued() + { + // notify all dequeued activities from last round + for( const auto& pActivity : maDequeuedActivities ) + pActivity->dequeued(); + maDequeuedActivities.clear(); + } + + bool ActivitiesQueue::isEmpty() const + { + return maCurrentActivitiesWaiting.empty() && maCurrentActivitiesReinsert.empty(); + } + + void ActivitiesQueue::clear() + { + // dequeue all entries: + for( const auto& pActivity : maCurrentActivitiesWaiting ) + pActivity->dequeued(); + ActivityQueue().swap( maCurrentActivitiesWaiting ); + + for( const auto& pActivity : maCurrentActivitiesReinsert ) + pActivity->dequeued(); + ActivityQueue().swap( maCurrentActivitiesReinsert ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |