diff options
Diffstat (limited to 'slideshow/source/engine')
177 files changed, 47610 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: */ diff --git a/slideshow/source/engine/animatedsprite.cxx b/slideshow/source/engine/animatedsprite.cxx new file mode 100644 index 000000000..94ee9e313 --- /dev/null +++ b/slideshow/source/engine/animatedsprite.cxx @@ -0,0 +1,199 @@ +/* -*- 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 <animatedsprite.hxx> + +#include <cppcanvas/canvas.hxx> +#include <cppcanvas/customsprite.hxx> +#include <canvas/canvastools.hxx> + +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + AnimatedSprite::AnimatedSprite( const ViewLayerSharedPtr& rViewLayer, + const ::basegfx::B2DSize& rSpriteSizePixel, + double nSpritePrio ) : + mpViewLayer( rViewLayer ), + mpSprite(), + maEffectiveSpriteSizePixel( rSpriteSizePixel ), + maContentPixelOffset(), + mnSpritePrio(nSpritePrio), + mnAlpha(0.0), + maPosPixel(), + maClip(), + mbSpriteVisible( false ) + { + ENSURE_OR_THROW( mpViewLayer, "AnimatedSprite::AnimatedSprite(): Invalid view layer" ); + + // Add half a pixel tolerance to sprite size, since we later on compare + // against it in resize(). And view transformations will almost never yield + // the same data bits when transforming to device coordinates + maEffectiveSpriteSizePixel += ::basegfx::B2DSize(0.5, 0.5); + + mpSprite = mpViewLayer->createSprite( maEffectiveSpriteSizePixel, + mnSpritePrio ); + + ENSURE_OR_THROW( mpSprite, "AnimatedSprite::AnimatedSprite(): Could not create sprite" ); + } + + ::cppcanvas::CanvasSharedPtr AnimatedSprite::getContentCanvas() const + { + ENSURE_OR_THROW( mpViewLayer->getCanvas(), "AnimatedSprite::getContentCanvas(): No view layer canvas" ); + + const ::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() ); + pContentCanvas->clear(); + + // extract linear part of canvas view transformation + // (linear means: without translational components). The + // only translation that is imposed at the view transform + // is the local content pixel offset. + + // We can apply that directly here, no need to call + // aLinearTransform.translate(), since, as said above, the + // last column of aLinearTransform is assumed [0 0 1] + ::basegfx::B2DHomMatrix aLinearTransform( mpViewLayer->getTransformation() ); + aLinearTransform.set( 0, 2, maContentPixelOffset.getX() ); + aLinearTransform.set( 1, 2, maContentPixelOffset.getY() ); + + // apply linear part of canvas view transformation to sprite canvas + pContentCanvas->setTransformation( aLinearTransform ); + + return pContentCanvas; + } + + void AnimatedSprite::resize( const ::basegfx::B2DSize& rSpriteSizePixel ) + { + // Enlarge or reduce the sprite size, if necessary. This + // method employs a strategy similar to container, when + // allocating memory: size is doubled or halved every time + // the limit is reached. This makes for amortized constant + // time in runtime complexity. Note that we take exact + // powers of two here, since several HW-accelerated canvas + // implementations are limited to such sprite sizes + // (otherwise, those implementations would internally + // round up, too, wasting precious mem). + ::basegfx::B2DSize aNewSize( maEffectiveSpriteSizePixel ); + bool bNeedResize( false ); + + if( rSpriteSizePixel.getX() > maEffectiveSpriteSizePixel.getX() || + rSpriteSizePixel.getX() < 0.5*maEffectiveSpriteSizePixel.getX() ) + { + // enlarge or shrink width + aNewSize.setX( ::canvas::tools::nextPow2( ::basegfx::fround(rSpriteSizePixel.getX()) ) ); + bNeedResize = true; + } + + if( rSpriteSizePixel.getY() > maEffectiveSpriteSizePixel.getY() || + rSpriteSizePixel.getY() < 0.5*maEffectiveSpriteSizePixel.getY() ) + { + // enlarge or shrink height, by doubling it + aNewSize.setY( ::canvas::tools::nextPow2( ::basegfx::fround(rSpriteSizePixel.getY()) ) ); + bNeedResize = true; + } + + if( !bNeedResize ) + return; + + // as the old sprite might have already been altered + // (and therefore been placed in the update list of + // the spritecanvas for this frame), must hide it + // here, to ensure it's not visible on screen any + // longer. + mpSprite->hide(); + + maEffectiveSpriteSizePixel = aNewSize; + mpSprite = mpViewLayer->createSprite( maEffectiveSpriteSizePixel, + mnSpritePrio ); + + ENSURE_OR_THROW( mpSprite, + "AnimatedSprite::resize(): Could not create new sprite" ); + + // set attributes similar to previous sprite + if (mbSpriteVisible) + { + mpSprite->show(); + mpSprite->setAlpha( mnAlpha ); + + if( maPosPixel ) + mpSprite->movePixel( *maPosPixel ); + + if( maClip ) + mpSprite->setClip( *maClip ); + } + } + + void AnimatedSprite::setPixelOffset( const ::basegfx::B2DSize& rPixelOffset ) + { + maContentPixelOffset = rPixelOffset; + } + + void AnimatedSprite::movePixel( const ::basegfx::B2DPoint& rNewPos ) + { + maPosPixel = rNewPos; + mpSprite->movePixel( rNewPos ); + } + + void AnimatedSprite::setAlpha( double nAlpha ) + { + mnAlpha = nAlpha; + mpSprite->setAlpha( nAlpha ); + } + + void AnimatedSprite::clip( const ::basegfx::B2DPolyPolygon& rClip ) + { + maClip = rClip; + mpSprite->setClipPixel( rClip ); + } + + void AnimatedSprite::clip() + { + maClip.reset(); + mpSprite->setClip(); + } + + void AnimatedSprite::transform( const ::basegfx::B2DHomMatrix& rTransform ) + { + mpSprite->transform( rTransform ); + } + + void AnimatedSprite::hide() + { + mpSprite->hide(); + mbSpriteVisible = false; + } + + void AnimatedSprite::show() + { + mbSpriteVisible = true; + mpSprite->show(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/animationfactory.cxx b/slideshow/source/engine/animationfactory.cxx new file mode 100644 index 000000000..0d160e3f5 --- /dev/null +++ b/slideshow/source/engine/animationfactory.cxx @@ -0,0 +1,1515 @@ +/* -*- 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 <sal/log.hxx> + +#include <animationfactory.hxx> +#include <attributemap.hxx> + +#include <com/sun/star/animations/AnimationAdditiveMode.hpp> +#include <com/sun/star/animations/AnimationTransformType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> + +#include <box2dtools.hxx> + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + namespace + { + // attention, there is a similar implementation of Animation in + // transitions/transitionfactory.cxx + + template< typename ValueT > class TupleAnimation : public PairAnimation + { + public: + TupleAnimation( const ShapeManagerSharedPtr& rShapeManager, + int nFlags, + bool (ShapeAttributeLayer::*pIs1stValid)() const, + bool (ShapeAttributeLayer::*pIs2ndValid)() const, + const ValueT& rDefaultValue, + const ::basegfx::B2DSize& rReferenceSize, + double (ShapeAttributeLayer::*pGet1stValue)() const, + double (ShapeAttributeLayer::*pGet2ndValue)() const, + void (ShapeAttributeLayer::*pSetValue)( const ValueT& ) ) : + mpShape(), + mpAttrLayer(), + mpShapeManager( rShapeManager ), + mpIs1stValidFunc(pIs1stValid), + mpIs2ndValidFunc(pIs2ndValid), + mpGet1stValueFunc(pGet1stValue), + mpGet2ndValueFunc(pGet2ndValue), + mpSetValueFunc(pSetValue), + mnFlags( nFlags ), + maReferenceSize( rReferenceSize ), + maDefaultValue( rDefaultValue ), + mbAnimationStarted( false ) + { + ENSURE_OR_THROW( rShapeManager, + "TupleAnimation::TupleAnimation(): Invalid ShapeManager" ); + ENSURE_OR_THROW( pIs1stValid && pIs2ndValid && pGet1stValue && pGet2ndValue && pSetValue, + "TupleAnimation::TupleAnimation(): One of the method pointers is NULL" ); + } + + virtual ~TupleAnimation() override + { + end_(); + } + + // Animation interface + + virtual void prefetch() override + {} + + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override + { + OSL_ENSURE( !mpShape, + "TupleAnimation::start(): Shape already set" ); + OSL_ENSURE( !mpAttrLayer, + "TupleAnimation::start(): Attribute layer already set" ); + + mpShape = rShape; + mpAttrLayer = rAttrLayer; + + ENSURE_OR_THROW( rShape, + "TupleAnimation::start(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "TupleAnimation::start(): Invalid attribute layer" ); + + if( !mbAnimationStarted ) + { + mbAnimationStarted = true; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->enterAnimationMode( mpShape ); + } + } + + virtual void end() override { end_(); } + void end_() + { + if( mbAnimationStarted ) + { + mbAnimationStarted = false; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->leaveAnimationMode( mpShape ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + } + } + + // PairAnimation interface + + + virtual bool operator()( const ::basegfx::B2DTuple& rValue ) override + { + ENSURE_OR_RETURN_FALSE( mpAttrLayer && mpShape, + "TupleAnimation::operator(): Invalid ShapeAttributeLayer" ); + + ValueT aValue( rValue.getX(), + rValue.getY() ); + + // Activities get values from the expression parser, + // which returns _relative_ sizes/positions. + // Convert back relative to reference coordinate system + aValue *= maReferenceSize; + + ((*mpAttrLayer).*mpSetValueFunc)( aValue ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + return true; + } + + virtual ::basegfx::B2DTuple getUnderlyingValue() const override + { + ENSURE_OR_THROW( mpAttrLayer, + "TupleAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" ); + + ::basegfx::B2DTuple aRetVal; + + // deviated from the (*shared_ptr).*mpFuncPtr + // notation here, since gcc does not seem to parse + // that as a member function call anymore. + aRetVal.setX( (mpAttrLayer.get()->*mpIs1stValidFunc)() ? + (mpAttrLayer.get()->*mpGet1stValueFunc)() : + maDefaultValue.getX() ); + aRetVal.setY( (mpAttrLayer.get()->*mpIs2ndValidFunc)() ? + (mpAttrLayer.get()->*mpGet2ndValueFunc)() : + maDefaultValue.getY() ); + + // Activities get values from the expression + // parser, which returns _relative_ + // sizes/positions. Convert start value to the + // same coordinate space (i.e. relative to given + // reference size). + aRetVal /= maReferenceSize; + + return aRetVal; + } + + private: + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttrLayer; + ShapeManagerSharedPtr mpShapeManager; + bool (ShapeAttributeLayer::*mpIs1stValidFunc)() const; + bool (ShapeAttributeLayer::*mpIs2ndValidFunc)() const; + double (ShapeAttributeLayer::*mpGet1stValueFunc)() const; + double (ShapeAttributeLayer::*mpGet2ndValueFunc)() const; + void (ShapeAttributeLayer::*mpSetValueFunc)( const ValueT& ); + + const int mnFlags; + + const ::basegfx::B2DSize maReferenceSize; + const ValueT maDefaultValue; + bool mbAnimationStarted; + }; + + + class PathAnimation : public NumberAnimation + { + public: + PathAnimation( std::u16string_view rSVGDPath, + sal_Int16 nAdditive, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + int nFlags, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld ) : + maPathPoly(), + mpShape(), + mpAttrLayer(), + mpShapeManager( rShapeManager ), + maPageSize( rSlideSize ), + maShapeOrig(), + mnFlags( nFlags ), + mbAnimationStarted( false ), + mbAnimationFirstUpdate( true ), + mnAdditive( nAdditive ), + mpBox2DWorld( pBox2DWorld ) + { + ENSURE_OR_THROW( rShapeManager, + "PathAnimation::PathAnimation(): Invalid ShapeManager" ); + + ::basegfx::B2DPolyPolygon aPolyPoly; + + ENSURE_OR_THROW( ::basegfx::utils::importFromSvgD( aPolyPoly, rSVGDPath, false, nullptr ), + "PathAnimation::PathAnimation(): failed to parse SVG:d path" ); + ENSURE_OR_THROW( aPolyPoly.count() == 1, + "PathAnimation::PathAnimation(): motion path consists of multiple/zero polygon(s)" ); + + maPathPoly = aPolyPoly.getB2DPolygon(0); + } + + virtual ~PathAnimation() override + { + end_(); + } + + // Animation interface + + virtual void prefetch() override + {} + + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override + { + OSL_ENSURE( !mpShape, + "PathAnimation::start(): Shape already set" ); + OSL_ENSURE( !mpAttrLayer, + "PathAnimation::start(): Attribute layer already set" ); + + mpShape = rShape; + mpAttrLayer = rAttrLayer; + + ENSURE_OR_THROW( rShape, + "PathAnimation::start(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "PathAnimation::start(): Invalid attribute layer" ); + + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + if( mnAdditive == animations::AnimationAdditiveMode::SUM ) + maShapeOrig = mpShape->getBounds().getCenter(); + else + maShapeOrig = mpShape->getDomBounds().getCenter(); + + if( !mbAnimationStarted ) + { + mbAnimationStarted = true; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->enterAnimationMode( mpShape ); + } + } + + virtual void end() override { end_(); } + void end_() + { + if( !mbAnimationStarted ) + return; + + mbAnimationStarted = false; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->leaveAnimationMode( mpShape ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + // if there is a physics animation going on report the animation ending + // and zero out the velocity of the shape + if( mpBox2DWorld->isInitialized() ) + mpBox2DWorld->queueLinearVelocityUpdate( mpShape->getXShape(), {0,0}); + } + + // NumberAnimation interface + + + virtual bool operator()( double nValue ) override + { + ENSURE_OR_RETURN_FALSE( mpAttrLayer && mpShape, + "PathAnimation::operator(): Invalid ShapeAttributeLayer" ); + + ::basegfx::B2DPoint rOutPos = ::basegfx::utils::getPositionRelative( maPathPoly, + nValue ); + + // TODO(F1): Determine whether the path is + // absolute, or shape-relative. + + // interpret path as page-relative. Scale up with page size + rOutPos *= maPageSize; + + // TODO(F1): Determine whether the path origin is + // absolute, or shape-relative. + + // interpret path as shape-originated. Offset to shape position + + rOutPos += maShapeOrig; + + mpAttrLayer->setPosition( rOutPos ); + + if( mpShape->isContentChanged() ) + { + mpShapeManager->notifyShapeUpdate( mpShape ); + + // if there's a physics animation going on report the change to it + if ( mpBox2DWorld->isInitialized() ) + { + mpBox2DWorld->queueShapePathAnimationUpdate( mpShape->getXShape(), + mpAttrLayer, + mbAnimationFirstUpdate ); + } + } + + if( mbAnimationFirstUpdate ) mbAnimationFirstUpdate = false; + + return true; + } + + virtual double getUnderlyingValue() const override + { + ENSURE_OR_THROW( mpAttrLayer, + "PathAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" ); + + return 0.0; // though this should be used in concert with + // ActivitiesFactory::createSimpleActivity, better + // explicitly name our start value. + // Permissible range for operator() above is [0,1] + } + + private: + ::basegfx::B2DPolygon maPathPoly; + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttrLayer; + ShapeManagerSharedPtr mpShapeManager; + const ::basegfx::B2DSize maPageSize; + ::basegfx::B2DPoint maShapeOrig; + const int mnFlags; + bool mbAnimationStarted; + bool mbAnimationFirstUpdate; + sal_Int16 mnAdditive; + box2d::utils::Box2DWorldSharedPtr mpBox2DWorld; + }; + + class PhysicsAnimation : public NumberAnimation + { + public: + PhysicsAnimation( const ::box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + const double fDuration, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const ::basegfx::B2DVector& rStartVelocity, + const double fDensity, + const double fBounciness, + int nFlags ) : + mpShape(), + mpAttrLayer(), + mpShapeManager( rShapeManager ), + maPageSize( rSlideSize ), + mnFlags( nFlags ), + mbAnimationStarted( false ), + mpBox2DBody(), + mpBox2DWorld( pBox2DWorld ), + mfDuration(fDuration), + maStartVelocity(rStartVelocity), + mfDensity(fDensity), + mfBounciness(fBounciness), + mfPreviousElapsedTime(0.00f), + mbIsBox2dWorldStepper(false) + { + ENSURE_OR_THROW( rShapeManager, + "PhysicsAnimation::PhysicsAnimation(): Invalid ShapeManager" ); + } + + virtual ~PhysicsAnimation() override + { + end_(); + } + + // Animation interface + + virtual void prefetch() override + {} + + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override + { + OSL_ENSURE( !mpShape, + "PhysicsAnimation::start(): Shape already set" ); + OSL_ENSURE( !mpAttrLayer, + "PhysicsAnimation::start(): Attribute layer already set" ); + + mpShape = rShape; + mpAttrLayer = rAttrLayer; + + ENSURE_OR_THROW( rShape, + "PhysicsAnimation::start(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "PhysicsAnimation::start(): Invalid attribute layer" ); + + if( !mbAnimationStarted ) + { + mbAnimationStarted = true; + + mpBox2DWorld->alertPhysicsAnimationStart(maPageSize, mpShapeManager); + mpBox2DBody = mpBox2DWorld->makeShapeDynamic( mpShape->getXShape(), maStartVelocity, mfDensity, mfBounciness ); + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->enterAnimationMode( mpShape ); + } + } + + virtual void end() override { end_(); } + void end_() + { + if( mbIsBox2dWorldStepper ) + { + mbIsBox2dWorldStepper = false; + mpBox2DWorld->setHasWorldStepper(false); + } + + if( !mbAnimationStarted ) + return; + + mbAnimationStarted = false; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->leaveAnimationMode( mpShape ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + mpBox2DWorld->alertPhysicsAnimationEnd(mpShape); + // if this was the only physics animation effect going on + // all box2d bodies were destroyed on alertPhysicsAnimationEnd + // except the one owned by the animation. + // Try to destroy the remaining body - if it is unique + // (it being unique means all physics animation effects have ended + // since otherwise mpBox2DWorld would own a copy of the shared_ptr ) + mpBox2DBody.reset(); + + } + + // NumberAnimation interface + + + virtual bool operator()( double nValue ) override + { + ENSURE_OR_RETURN_FALSE( mpAttrLayer && mpShape, + "PhysicsAnimation::operator(): Invalid ShapeAttributeLayer" ); + + // if there are multiple physics animations going in parallel + // Only one of them should step the box2d world + if( !mpBox2DWorld->hasWorldStepper() ) + { + mbIsBox2dWorldStepper = true; + mpBox2DWorld->setHasWorldStepper(true); + } + + if( mbIsBox2dWorldStepper ) + { + double fPassedTime = (mfDuration * nValue) - mfPreviousElapsedTime; + mfPreviousElapsedTime += mpBox2DWorld->stepAmount( fPassedTime ); + } + + mpAttrLayer->setPosition( mpBox2DBody->getPosition() ); + mpAttrLayer->setRotationAngle( mpBox2DBody->getAngle() ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + return true; + } + + virtual double getUnderlyingValue() const override + { + ENSURE_OR_THROW( mpAttrLayer, + "PhysicsAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" ); + + return 0.0; + } + + private: + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttrLayer; + ShapeManagerSharedPtr mpShapeManager; + const ::basegfx::B2DSize maPageSize; + const int mnFlags; + bool mbAnimationStarted; + box2d::utils::Box2DBodySharedPtr mpBox2DBody; + box2d::utils::Box2DWorldSharedPtr mpBox2DWorld; + double mfDuration; + const ::basegfx::B2DVector maStartVelocity; + const double mfDensity; + const double mfBounciness; + double mfPreviousElapsedTime; + bool mbIsBox2dWorldStepper; + }; + + /** GenericAnimation template + + This template makes heavy use of SFINAE, only one of + the operator()() methods will compile for each of the + base classes. + + Note that we omit the virtual keyword on the + operator()() overrides and getUnderlyingValue() methods 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 + those methods in the vtable actually creates POIs for + them, which breaks the whole SFINAE concept (IOW, this + template won't compile any longer). + + @tpl AnimationBase + Type of animation to generate (determines the + interface GenericAnimation will implement). Must be + one of NumberAnimation, ColorAnimation, + StringAnimation, PairAnimation or BoolAnimation. + + @tpl ModifierFunctor + Type of a functor object, which can optionally be used to + modify the getter/setter values. + */ + template< typename AnimationBase, typename ModifierFunctor > class GenericAnimation : public AnimationBase + { + public: + typedef typename AnimationBase::ValueType ValueT; + + /** Create generic animation + + @param pIsValid + Function pointer to one of the is*Valid + methods. Used to either take the given getter + method, or the given default value for the start value. + + @param rDefaultValue + Default value, to take as the start value if + is*Valid returns false. + + @param pGetValue + Getter method, to fetch start value if valid. + + @param pSetValue + Setter method. This one puts the current animation + value to the ShapeAttributeLayer. + + @param rGetterModifier + Modifies up values retrieved from the pGetValue method. + Must provide operator()( const ValueT& ) method. + + @param rSetterModifier + Modifies up values before passing them to the pSetValue method. + Must provide operator()( const ValueT& ) method. + */ + GenericAnimation( const ShapeManagerSharedPtr& rShapeManager, + int nFlags, + bool (ShapeAttributeLayer::*pIsValid)() const, + const ValueT& rDefaultValue, + ValueT (ShapeAttributeLayer::*pGetValue)() const, + void (ShapeAttributeLayer::*pSetValue)( const ValueT& ), + const ModifierFunctor& rGetterModifier, + const ModifierFunctor& rSetterModifier, + const AttributeType eAttrType, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld ) : + mpShape(), + mpAttrLayer(), + mpShapeManager( rShapeManager ), + mpIsValidFunc(pIsValid), + mpGetValueFunc(pGetValue), + mpSetValueFunc(pSetValue), + maGetterModifier( rGetterModifier ), + maSetterModifier( rSetterModifier ), + mnFlags( nFlags ), + maDefaultValue(rDefaultValue), + mbAnimationStarted( false ), + mbAnimationFirstUpdate( true ), + meAttrType( eAttrType ), + mpBox2DWorld ( pBox2DWorld ) + { + ENSURE_OR_THROW( rShapeManager, + "GenericAnimation::GenericAnimation(): Invalid ShapeManager" ); + ENSURE_OR_THROW( pIsValid && pGetValue && pSetValue, + "GenericAnimation::GenericAnimation(): One of the method pointers is NULL" ); + } + + ~GenericAnimation() + { + end(); + } + + // Animation interface + + virtual void prefetch() + {} + + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) + { + OSL_ENSURE( !mpShape, + "GenericAnimation::start(): Shape already set" ); + OSL_ENSURE( !mpAttrLayer, + "GenericAnimation::start(): Attribute layer already set" ); + + mpShape = rShape; + mpAttrLayer = rAttrLayer; + + ENSURE_OR_THROW( rShape, + "GenericAnimation::start(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "GenericAnimation::start(): Invalid attribute layer" ); + + // only start animation once per repeated start() call, + // and only if sprites should be used for display + if( !mbAnimationStarted ) + { + mbAnimationStarted = true; + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->enterAnimationMode( mpShape ); + } + } + + void end() + { + // TODO(Q2): Factor out common code (most + // prominently start() and end()) into base class + + // only stop animation once per repeated end() call, + // and only if sprites are used for display + if( !mbAnimationStarted ) + return; + + mbAnimationStarted = false; + + if( mpBox2DWorld && mpBox2DWorld->isInitialized() ) + { + // if there's a physics animation going on report the animation ending to it + mpBox2DWorld->queueShapeAnimationEndUpdate( mpShape->getXShape(), meAttrType ); + } + + if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) ) + mpShapeManager->leaveAnimationMode( mpShape ); + + // Attention, this notifyShapeUpdate() is + // somewhat delicate here. Calling it + // unconditional (i.e. not guarded by + // mbAnimationStarted) will lead to shapes + // snapping back to their original state just + // before the slide ends. Not calling it at + // all might swallow final animation + // states. The current implementation relies + // on the fact that end() is either called by + // the Activity (then, the last animation + // state has been set, and corresponds to the + // shape's hold state), or by the animation + // node (then, it's a forced end, and we + // _have_ to snap back). + + // To reiterate: normally, we're called from + // the Activity first, thus the + // notifyShapeUpdate() below will update to + // the last activity value. + + // force shape update, activity might have changed + // state in the last round. + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + } + + // Derived Animation interface + + + /** For by-reference interfaces (B2DTuple, OUString) + */ + bool operator()( const ValueT& x ) + { + ENSURE_OR_RETURN_FALSE( mpAttrLayer && mpShape, + "GenericAnimation::operator(): Invalid ShapeAttributeLayer" ); + + ((*mpAttrLayer).*mpSetValueFunc)( maSetterModifier( x ) ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + if( mbAnimationFirstUpdate ) mbAnimationFirstUpdate = false; + + return true; + } + + /** For by-value interfaces (bool, double) + */ + bool operator()( ValueT x ) + { + ENSURE_OR_RETURN_FALSE( mpAttrLayer && mpShape, + "GenericAnimation::operator(): Invalid ShapeAttributeLayer" ); + + ((*mpAttrLayer).*mpSetValueFunc)( maSetterModifier( x ) ); + + if( mpBox2DWorld && mpBox2DWorld->isInitialized() ) + { + // if there's a physics animation going on report the change to it + mpBox2DWorld->queueShapeAnimationUpdate( mpShape->getXShape(), mpAttrLayer, meAttrType, mbAnimationFirstUpdate ); + } + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + if( mbAnimationFirstUpdate ) mbAnimationFirstUpdate = false; + + return true; + } + + ValueT getUnderlyingValue() const + { + ENSURE_OR_THROW( mpAttrLayer, + "GenericAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" ); + + // deviated from the (*shared_ptr).*mpFuncPtr + // notation here, since gcc does not seem to parse + // that as a member function call anymore. + if( (mpAttrLayer.get()->*mpIsValidFunc)() ) + return maGetterModifier( ((*mpAttrLayer).*mpGetValueFunc)() ); + else + return maDefaultValue; + } + + private: + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttrLayer; + ShapeManagerSharedPtr mpShapeManager; + bool (ShapeAttributeLayer::*mpIsValidFunc)() const; + ValueT (ShapeAttributeLayer::*mpGetValueFunc)() const; + void (ShapeAttributeLayer::*mpSetValueFunc)( const ValueT& ); + + ModifierFunctor maGetterModifier; + ModifierFunctor maSetterModifier; + + const int mnFlags; + + const ValueT maDefaultValue; + bool mbAnimationStarted; + bool mbAnimationFirstUpdate; + + const AttributeType meAttrType; + const box2d::utils::Box2DWorldSharedPtr mpBox2DWorld; + }; + + //Current c++0x draft (apparently) has std::identity, but not operator() + template<typename T> struct SGI_identity + { + T& operator()(T& x) const { return x; } + const T& operator()(const T& x) const { return x; } + }; + + /** Function template wrapper around GenericAnimation template + + @tpl AnimationBase + Type of animation to generate (determines the + interface GenericAnimation will implement). + */ + template< typename AnimationBase > ::std::shared_ptr< AnimationBase > + makeGenericAnimation( const ShapeManagerSharedPtr& rShapeManager, + int nFlags, + bool (ShapeAttributeLayer::*pIsValid)() const, + const typename AnimationBase::ValueType& rDefaultValue, + typename AnimationBase::ValueType (ShapeAttributeLayer::*pGetValue)() const, + void (ShapeAttributeLayer::*pSetValue)( const typename AnimationBase::ValueType& ), + const AttributeType eAttrType, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld ) + { + return std::make_shared<GenericAnimation< AnimationBase, + SGI_identity< typename AnimationBase::ValueType > >>( + rShapeManager, + nFlags, + pIsValid, + rDefaultValue, + pGetValue, + pSetValue, + // no modification necessary, use identity functor here + SGI_identity< typename AnimationBase::ValueType >(), + SGI_identity< typename AnimationBase::ValueType >(), + eAttrType, + pBox2DWorld ); + } + + class Scaler + { + public: + explicit Scaler( double nScale ) : + mnScale( nScale ) + { + } + + double operator()( double nVal ) const + { + return mnScale * nVal; + } + + private: + double mnScale; + }; + + /** Overload for NumberAnimations which need scaling (width,height,x,y currently) + */ + NumberAnimationSharedPtr makeGenericAnimation( const ShapeManagerSharedPtr& rShapeManager, + int nFlags, + bool (ShapeAttributeLayer::*pIsValid)() const, + double nDefaultValue, + double (ShapeAttributeLayer::*pGetValue)() const, + void (ShapeAttributeLayer::*pSetValue)( const double& ), + double nScaleValue, + const AttributeType eAttrType, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld ) + { + return std::make_shared<GenericAnimation< NumberAnimation, Scaler >>( rShapeManager, + nFlags, + pIsValid, + nDefaultValue / nScaleValue, + pGetValue, + pSetValue, + Scaler( 1.0/nScaleValue ), + Scaler( nScaleValue ), + eAttrType, + pBox2DWorld ); + } + + + uno::Any getShapeDefault( const AnimatableShapeSharedPtr& rShape, + const OUString& rPropertyName ) + { + uno::Reference< drawing::XShape > xShape( rShape->getXShape() ); + + if( !xShape.is() ) + return uno::Any(); // no regular shape, no defaults available + + + // extract relevant value from XShape's PropertySet + uno::Reference< beans::XPropertySet > xPropSet( xShape, + uno::UNO_QUERY ); + + ENSURE_OR_THROW( xPropSet.is(), + "getShapeDefault(): Cannot query property set from shape" ); + + return xPropSet->getPropertyValue( rPropertyName ); + } + + template< typename ValueType > ValueType getDefault( const AnimatableShapeSharedPtr& rShape, + const OUString& rPropertyName ) + { + const uno::Any& rAny( getShapeDefault( rShape, + rPropertyName ) ); + + if( !rAny.hasValue() ) + { + SAL_WARN("slideshow", "getDefault(): cannot get shape property " << rPropertyName ); + return ValueType(); + } + else + { + ValueType aValue = ValueType(); + + if( !(rAny >>= aValue) ) + { + SAL_WARN("slideshow", "getDefault(): cannot extract shape property " << rPropertyName); + return ValueType(); + } + + return aValue; + } + } + + template<> RGBColor getDefault< RGBColor >( const AnimatableShapeSharedPtr& rShape, + const OUString& rPropertyName ) + { + const uno::Any& rAny( getShapeDefault( rShape, + rPropertyName ) ); + + if( !rAny.hasValue() ) + { + SAL_WARN("slideshow", "getDefault(): cannot get shape color property " << rPropertyName); + return RGBColor(); + } + else + { + sal_Int32 nValue = 0; + + if( !(rAny >>= nValue) ) + { + SAL_INFO("slideshow", "getDefault(): cannot extract shape color property " << rPropertyName); + return RGBColor(); + } + + // convert from 0xAARRGGBB API color to 0xRRGGBB00 + // canvas color + return RGBColor( (nValue << 8U) & 0xFFFFFF00U ); + } + } + } + + AnimationFactory::AttributeClass AnimationFactory::classifyAttributeName( const OUString& rAttrName ) + { + // ATTENTION: When changing this map, also the create*PropertyAnimation() methods must + // be checked and possibly adapted in their switch statements + + // TODO(Q2): Since this map must be coherent with the various switch statements + // in the create*PropertyAnimation methods, try to unify into a single method or table + switch( mapAttributeName( rAttrName ) ) + { + default: + case AttributeType::Invalid: + return CLASS_UNKNOWN_PROPERTY; + + case AttributeType::CharColor: + case AttributeType::Color: + case AttributeType::DimColor: + case AttributeType::FillColor: + case AttributeType::LineColor: + return CLASS_COLOR_PROPERTY; + + case AttributeType::CharFontName: + return CLASS_STRING_PROPERTY; + + case AttributeType::Visibility: + return CLASS_BOOL_PROPERTY; + + case AttributeType::CharHeight: + case AttributeType::CharWeight: + case AttributeType::Height: + case AttributeType::Opacity: + case AttributeType::Rotate: + case AttributeType::SkewX: + case AttributeType::SkewY: + case AttributeType::Width: + case AttributeType::PosX: + case AttributeType::PosY: + return CLASS_NUMBER_PROPERTY; + + case AttributeType::CharUnderline: + case AttributeType::FillStyle: + case AttributeType::LineStyle: + case AttributeType::CharPosture: + return CLASS_ENUM_PROPERTY; + } + } + + NumberAnimationSharedPtr AnimationFactory::createNumberPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + // ATTENTION: When changing this map, also the classifyAttributeName() method must + // be checked and possibly adapted in their switch statement + AttributeType eAttrType = mapAttributeName(rAttrName); + switch( eAttrType ) + { + default: + case AttributeType::Invalid: + ENSURE_OR_THROW( false, + "AnimationFactory::createNumberPropertyAnimation(): Unknown attribute" ); + break; + + case AttributeType::CharColor: + case AttributeType::CharFontName: + case AttributeType::CharPosture: + case AttributeType::CharUnderline: + case AttributeType::Color: + case AttributeType::DimColor: + case AttributeType::FillColor: + case AttributeType::FillStyle: + case AttributeType::LineColor: + case AttributeType::LineStyle: + case AttributeType::Visibility: + ENSURE_OR_THROW( false, + "AnimationFactory::createNumberPropertyAnimation(): Attribute type mismatch" ); + break; + + case AttributeType::CharHeight: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isCharScaleValid, + 1.0, // CharHeight is a relative attribute, thus + // default is 1.0 + &ShapeAttributeLayer::getCharScale, + &ShapeAttributeLayer::setCharScale, + eAttrType, + pBox2DWorld ); + + case AttributeType::CharWeight: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isCharWeightValid, + getDefault<double>( rShape, rAttrName ), + &ShapeAttributeLayer::getCharWeight, + &ShapeAttributeLayer::setCharWeight, + eAttrType, + pBox2DWorld ); + + case AttributeType::Height: + return makeGenericAnimation( rShapeManager, + nFlags, + &ShapeAttributeLayer::isHeightValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rShape->getBounds().getHeight(), + &ShapeAttributeLayer::getHeight, + &ShapeAttributeLayer::setHeight, + // convert expression parser value from relative page size + rSlideSize.getY(), + eAttrType, + pBox2DWorld ); + + case AttributeType::Opacity: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isAlphaValid, + // TODO(F1): Provide shape default here (FillTransparency?) + 1.0, + &ShapeAttributeLayer::getAlpha, + &ShapeAttributeLayer::setAlpha, + eAttrType, + pBox2DWorld ); + + case AttributeType::Rotate: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isRotationAngleValid, + // NOTE: Since we paint the shape as-is from metafile, + // rotation angle is always 0.0, even for rotated shapes + 0.0, + &ShapeAttributeLayer::getRotationAngle, + &ShapeAttributeLayer::setRotationAngle, + eAttrType, + pBox2DWorld ); + + case AttributeType::SkewX: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isShearXAngleValid, + // TODO(F1): Is there any shape property for skew? + 0.0, + &ShapeAttributeLayer::getShearXAngle, + &ShapeAttributeLayer::setShearXAngle, + eAttrType, + pBox2DWorld ); + + case AttributeType::SkewY: + return makeGenericAnimation<NumberAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isShearYAngleValid, + // TODO(F1): Is there any shape property for skew? + 0.0, + &ShapeAttributeLayer::getShearYAngle, + &ShapeAttributeLayer::setShearYAngle, + eAttrType, + pBox2DWorld ); + + case AttributeType::Width: + return makeGenericAnimation( rShapeManager, + nFlags, + &ShapeAttributeLayer::isWidthValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rShape->getBounds().getWidth(), + &ShapeAttributeLayer::getWidth, + &ShapeAttributeLayer::setWidth, + // convert expression parser value from relative page size + rSlideSize.getX(), + eAttrType, + pBox2DWorld ); + + case AttributeType::PosX: + return makeGenericAnimation( rShapeManager, + nFlags, + &ShapeAttributeLayer::isPosXValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rShape->getBounds().getCenterX(), + &ShapeAttributeLayer::getPosX, + &ShapeAttributeLayer::setPosX, + // convert expression parser value from relative page size + rSlideSize.getX(), + eAttrType, + pBox2DWorld ); + + case AttributeType::PosY: + return makeGenericAnimation( rShapeManager, + nFlags, + &ShapeAttributeLayer::isPosYValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rShape->getBounds().getCenterY(), + &ShapeAttributeLayer::getPosY, + &ShapeAttributeLayer::setPosY, + // convert expression parser value from relative page size + rSlideSize.getY(), + eAttrType, + pBox2DWorld ); + } + + return NumberAnimationSharedPtr(); + } + + EnumAnimationSharedPtr AnimationFactory::createEnumPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& /*rSlideSize*/, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + // ATTENTION: When changing this map, also the classifyAttributeName() method must + // be checked and possibly adapted in their switch statement + AttributeType eAttrType = mapAttributeName( rAttrName ); + switch( eAttrType ) + { + default: + case AttributeType::Invalid: + ENSURE_OR_THROW( false, + "AnimationFactory::createEnumPropertyAnimation(): Unknown attribute" ); + break; + + case AttributeType::CharColor: + case AttributeType::CharFontName: + case AttributeType::Color: + case AttributeType::DimColor: + case AttributeType::FillColor: + case AttributeType::LineColor: + case AttributeType::Visibility: + case AttributeType::CharHeight: + case AttributeType::CharWeight: + case AttributeType::Height: + case AttributeType::Opacity: + case AttributeType::Rotate: + case AttributeType::SkewX: + case AttributeType::SkewY: + case AttributeType::Width: + case AttributeType::PosX: + case AttributeType::PosY: + ENSURE_OR_THROW( false, + "AnimationFactory::createEnumPropertyAnimation(): Attribute type mismatch" ); + break; + + + case AttributeType::FillStyle: + return makeGenericAnimation<EnumAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isFillStyleValid, + sal::static_int_cast<sal_Int16>( + getDefault<drawing::FillStyle>( rShape, rAttrName )), + &ShapeAttributeLayer::getFillStyle, + &ShapeAttributeLayer::setFillStyle, + eAttrType, + pBox2DWorld ); + + case AttributeType::LineStyle: + return makeGenericAnimation<EnumAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isLineStyleValid, + sal::static_int_cast<sal_Int16>( + getDefault<drawing::LineStyle>( rShape, rAttrName )), + &ShapeAttributeLayer::getLineStyle, + &ShapeAttributeLayer::setLineStyle, + eAttrType, + pBox2DWorld ); + + case AttributeType::CharPosture: + return makeGenericAnimation<EnumAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isCharPostureValid, + sal::static_int_cast<sal_Int16>( + getDefault<awt::FontSlant>( rShape, rAttrName )), + &ShapeAttributeLayer::getCharPosture, + &ShapeAttributeLayer::setCharPosture, + eAttrType, + pBox2DWorld ); + + case AttributeType::CharUnderline: + return makeGenericAnimation<EnumAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isUnderlineModeValid, + getDefault<sal_Int16>( rShape, rAttrName ), + &ShapeAttributeLayer::getUnderlineMode, + &ShapeAttributeLayer::setUnderlineMode, + eAttrType, + pBox2DWorld ); + } + + return EnumAnimationSharedPtr(); + } + + ColorAnimationSharedPtr AnimationFactory::createColorPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& /*rSlideSize*/, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + // ATTENTION: When changing this map, also the classifyAttributeName() method must + // be checked and possibly adapted in their switch statement + AttributeType eAttrType = mapAttributeName(rAttrName); + switch( eAttrType ) + { + default: + case AttributeType::Invalid: + ENSURE_OR_THROW( false, + "AnimationFactory::createColorPropertyAnimation(): Unknown attribute" ); + break; + + case AttributeType::CharFontName: + case AttributeType::CharHeight: + case AttributeType::CharPosture: + case AttributeType::CharUnderline: + case AttributeType::CharWeight: + case AttributeType::FillStyle: + case AttributeType::Height: + case AttributeType::LineStyle: + case AttributeType::Opacity: + case AttributeType::Rotate: + case AttributeType::SkewX: + case AttributeType::SkewY: + case AttributeType::Visibility: + case AttributeType::Width: + case AttributeType::PosX: + case AttributeType::PosY: + ENSURE_OR_THROW( false, + "AnimationFactory::createColorPropertyAnimation(): Attribute type mismatch" ); + break; + + case AttributeType::CharColor: + return makeGenericAnimation<ColorAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isCharColorValid, + getDefault<RGBColor>( rShape, rAttrName ), + &ShapeAttributeLayer::getCharColor, + &ShapeAttributeLayer::setCharColor, + eAttrType, + pBox2DWorld ); + + case AttributeType::Color: + // TODO(F2): This is just mapped to fill color to make it work + return makeGenericAnimation<ColorAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isFillColorValid, + getDefault<RGBColor>( rShape, rAttrName ), + &ShapeAttributeLayer::getFillColor, + &ShapeAttributeLayer::setFillColor, + eAttrType, + pBox2DWorld ); + + case AttributeType::DimColor: + return makeGenericAnimation<ColorAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isDimColorValid, + getDefault<RGBColor>( rShape, rAttrName ), + &ShapeAttributeLayer::getDimColor, + &ShapeAttributeLayer::setDimColor, + eAttrType, + pBox2DWorld ); + + case AttributeType::FillColor: + return makeGenericAnimation<ColorAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isFillColorValid, + getDefault<RGBColor>( rShape, rAttrName ), + &ShapeAttributeLayer::getFillColor, + &ShapeAttributeLayer::setFillColor, + eAttrType, + pBox2DWorld ); + + case AttributeType::LineColor: + return makeGenericAnimation<ColorAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isLineColorValid, + getDefault<RGBColor>( rShape, rAttrName ), + &ShapeAttributeLayer::getLineColor, + &ShapeAttributeLayer::setLineColor, + eAttrType, + pBox2DWorld ); + } + + return ColorAnimationSharedPtr(); + } + + PairAnimationSharedPtr AnimationFactory::createPairPropertyAnimation( const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + sal_Int16 nTransformType, + int nFlags ) + { + const ::basegfx::B2DRectangle& rBounds( rShape->getBounds() ); + + switch( nTransformType ) + { + case animations::AnimationTransformType::SCALE: + return std::make_shared<TupleAnimation< ::basegfx::B2DSize >>( + rShapeManager, + nFlags, + &ShapeAttributeLayer::isWidthValid, + &ShapeAttributeLayer::isHeightValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rBounds.getRange(), + rBounds.getRange(), + &ShapeAttributeLayer::getWidth, + &ShapeAttributeLayer::getHeight, + &ShapeAttributeLayer::setSize ); + + case animations::AnimationTransformType::TRANSLATE: + return std::make_shared<TupleAnimation< ::basegfx::B2DPoint >>( + rShapeManager, + nFlags, + &ShapeAttributeLayer::isPosXValid, + &ShapeAttributeLayer::isPosYValid, + // TODO(F1): Check whether _shape_ bounds are correct here. + // Theoretically, our AttrLayer is way down the stack, and + // we only have to consider _that_ value, not the one from + // the top of the stack as returned by Shape::getBounds() + rBounds.getCenter(), + rSlideSize, + &ShapeAttributeLayer::getPosX, + &ShapeAttributeLayer::getPosY, + &ShapeAttributeLayer::setPosition ); + + default: + ENSURE_OR_THROW( false, + "AnimationFactory::createPairPropertyAnimation(): Attribute type mismatch" ); + break; + } + + return PairAnimationSharedPtr(); + } + + StringAnimationSharedPtr AnimationFactory::createStringPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& /*rSlideSize*/, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + // ATTENTION: When changing this map, also the classifyAttributeName() method must + // be checked and possibly adapted in their switch statement + AttributeType eAttrType = mapAttributeName(rAttrName); + switch( eAttrType ) + { + default: + case AttributeType::Invalid: + ENSURE_OR_THROW( false, + "AnimationFactory::createStringPropertyAnimation(): Unknown attribute" ); + break; + + case AttributeType::CharColor: + case AttributeType::CharHeight: + case AttributeType::CharUnderline: + case AttributeType::Color: + case AttributeType::DimColor: + case AttributeType::FillColor: + case AttributeType::Height: + case AttributeType::LineColor: + case AttributeType::Opacity: + case AttributeType::Rotate: + case AttributeType::SkewX: + case AttributeType::SkewY: + case AttributeType::Visibility: + case AttributeType::Width: + case AttributeType::PosX: + case AttributeType::PosY: + case AttributeType::CharPosture: + case AttributeType::CharWeight: + case AttributeType::FillStyle: + case AttributeType::LineStyle: + ENSURE_OR_THROW( false, + "AnimationFactory::createStringPropertyAnimation(): Attribute type mismatch" ); + break; + + case AttributeType::CharFontName: + return makeGenericAnimation<StringAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isFontFamilyValid, + getDefault< OUString >( rShape, rAttrName ), + &ShapeAttributeLayer::getFontFamily, + &ShapeAttributeLayer::setFontFamily, + eAttrType, + pBox2DWorld ); + } + + return StringAnimationSharedPtr(); + } + + BoolAnimationSharedPtr AnimationFactory::createBoolPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& /*rShape*/, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& /*rSlideSize*/, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + // ATTENTION: When changing this map, also the classifyAttributeName() method must + // be checked and possibly adapted in their switch statement + AttributeType eAttrType = mapAttributeName(rAttrName); + switch( eAttrType ) + { + default: + case AttributeType::Invalid: + ENSURE_OR_THROW( false, + "AnimationFactory::createBoolPropertyAnimation(): Unknown attribute" ); + break; + + case AttributeType::CharColor: + case AttributeType::CharFontName: + case AttributeType::CharHeight: + case AttributeType::CharPosture: + case AttributeType::CharWeight: + case AttributeType::Color: + case AttributeType::DimColor: + case AttributeType::FillColor: + case AttributeType::FillStyle: + case AttributeType::Height: + case AttributeType::LineColor: + case AttributeType::LineStyle: + case AttributeType::Opacity: + case AttributeType::Rotate: + case AttributeType::SkewX: + case AttributeType::SkewY: + case AttributeType::Width: + case AttributeType::PosX: + case AttributeType::PosY: + case AttributeType::CharUnderline: + ENSURE_OR_THROW( false, + "AnimationFactory::createBoolPropertyAnimation(): Attribute type mismatch" ); + break; + + case AttributeType::Visibility: + return makeGenericAnimation<BoolAnimation>( rShapeManager, + nFlags, + &ShapeAttributeLayer::isVisibilityValid, + // TODO(F1): Is there a corresponding shape property? + true, + &ShapeAttributeLayer::getVisibility, + &ShapeAttributeLayer::setVisibility, + eAttrType, + pBox2DWorld ); + } + + return BoolAnimationSharedPtr(); + } + + NumberAnimationSharedPtr AnimationFactory::createPathMotionAnimation( const OUString& rSVGDPath, + sal_Int16 nAdditive, + const AnimatableShapeSharedPtr& /*rShape*/, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ) + { + return std::make_shared<PathAnimation>( rSVGDPath, nAdditive, + rShapeManager, + rSlideSize, + nFlags, + pBox2DWorld); + } + + NumberAnimationSharedPtr AnimationFactory::createPhysicsAnimation( const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + const double fDuration, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const ::basegfx::B2DVector& rStartVelocity, + const double fDensity, + const double fBounciness, + int nFlags ) + { + return std::make_shared<PhysicsAnimation>( pBox2DWorld, fDuration, + rShapeManager, + rSlideSize, + rStartVelocity, + fDensity, + fBounciness, + nFlags ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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: */ diff --git a/slideshow/source/engine/arith-grammar.txt b/slideshow/source/engine/arith-grammar.txt new file mode 100644 index 000000000..e5cb171f6 --- /dev/null +++ b/slideshow/source/engine/arith-grammar.txt @@ -0,0 +1,79 @@ +# +# 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 . +# + +Based on the C grammar for arithmetic expressions +================================================= + +number_digit = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' + +number_exponent = 'e'|'E' + +basic_number = basic_number number_digit | number_digit + +number = + basic_number | + + basic_number number_exponent basic_number | + basic_number number_exponent '-' basic_number | + basic_number number_exponent '+' basic_number | + + '.' basic_number number_exponent basic_number | + '.' basic_number number_exponent '-' basic_number | + '.' basic_number number_exponent '+' basic_number | + + basic_number '.' number_exponent basic_number | + basic_number '.' number_exponent '-' basic_number | + basic_number '.' number_exponent '+' basic_number | + + basic_number '.' basic_number number_exponent basic_number | + basic_number '.' basic_number number_exponent '-' basic_number | + basic_number '.' basic_number number_exponent '+' basic_number + + +identifier = '$'|'pi'|'e'|'X'|'Y'|'Width'|'Height' + ^ ^ ^ ^ ^ + | | | | | + '$' in PPT | | | | + '#ppt_x' in PPT | | | + '#ppt_y' in PPT | | + '#ppt_w' in PPT | + '#ppt_h' in PPT + +unary_function = 'abs'|'sqrt'|'sin'|'cos'|'tan'|'atan'|'acos'|'asin'|'exp'|'log' +binary_function = 'min'|'max' + + +basic_expression = + number | + identifier | + unary_function '(' additive_expression ')' | + binary_function '(' additive_expression ',' additive_expression ')' | + '(' additive_expression ')' + +unary_expression = '-' basic_expression + +multiplicative_expression = + basic_expression | + multiplicative_expression '*' basic_expression | + multiplicative_expression '/' basic_expression + +additive_expression = + multiplicative_expression | + additive_expression '+' multiplicative_expression | + additive_expression '-' multiplicative_expression + diff --git a/slideshow/source/engine/attributemap.cxx b/slideshow/source/engine/attributemap.cxx new file mode 100644 index 000000000..202ce1880 --- /dev/null +++ b/slideshow/source/engine/attributemap.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <canvas/canvastools.hxx> + +#include <attributemap.hxx> +#include <sal/log.hxx> + + +namespace slideshow::internal +{ + typedef ::canvas::tools::ValueMap< AttributeType > AnimateAttributeMap; + + AttributeType mapAttributeName( const OUString& rAttrName ) + { + /** Maps attribute name to AttributeType enum. + + String entries are all case-insensitive and MUST + BE STORED lowercase. + + String entries MUST BE SORTED in ascending order! + */ + static const AnimateAttributeMap::MapEntry lcl_attributeMap[] = + { + { "charcolor", AttributeType::CharColor }, + { "charfontname", AttributeType::CharFontName }, + { "charheight", AttributeType::CharHeight }, + { "charposture", AttributeType::CharPosture }, + // TODO(Q1): This should prolly be changed in PPT import + // { "charrotation", AttributeType::CharRotation }, + { "charrotation", AttributeType::Rotate }, + { "charunderline", AttributeType::CharUnderline }, + { "charweight", AttributeType::CharWeight }, + { "color", AttributeType::Color }, + { "dimcolor", AttributeType::DimColor }, + { "fillcolor", AttributeType::FillColor }, + { "fillstyle", AttributeType::FillStyle }, + { "height", AttributeType::Height }, + { "linecolor", AttributeType::LineColor }, + { "linestyle", AttributeType::LineStyle }, + { "opacity", AttributeType::Opacity }, + { "rotate", AttributeType::Rotate }, + { "skewx", AttributeType::SkewX }, + { "skewy", AttributeType::SkewY }, + { "visibility", AttributeType::Visibility }, + { "width", AttributeType::Width }, + { "x", AttributeType::PosX }, + { "y", AttributeType::PosY } + }; + + static const AnimateAttributeMap aMap( lcl_attributeMap, + SAL_N_ELEMENTS(lcl_attributeMap), + false ); + + AttributeType eAttributeType = AttributeType::Invalid; + + // determine the type from the attribute name + if( !aMap.lookup( rAttrName, + eAttributeType ) ) + { + SAL_WARN("slideshow", "mapAttributeName(): attribute name not found in map: " << rAttrName); + return AttributeType::Invalid; + } + + return eAttributeType; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/box2dtools.cxx b/slideshow/source/engine/box2dtools.cxx new file mode 100644 index 000000000..c6009afa6 --- /dev/null +++ b/slideshow/source/engine/box2dtools.cxx @@ -0,0 +1,893 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <box2dtools.hxx> +#include <config_box2d.h> +#include BOX2D_HEADER + +#include <shapemanager.hxx> +#include <attributableshape.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> + +#include <svx/svdobj.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdpage.hxx> + +#include <svx/unoapi.hxx> + +#define BOX2D_SLIDE_SIZE_IN_METERS 100.00f +constexpr double fDefaultStaticBodyBounciness(0.1); + +namespace box2d::utils +{ +namespace +{ +double calculateScaleFactor(const ::basegfx::B2DVector& rSlideSize) +{ + double fWidth = rSlideSize.getX(); + double fHeight = rSlideSize.getY(); + + // Scale factor is based on whatever is the larger + // value between slide width and height + if (fWidth > fHeight) + return BOX2D_SLIDE_SIZE_IN_METERS / fWidth; + else + return BOX2D_SLIDE_SIZE_IN_METERS / fHeight; +} + +b2BodyType getBox2DInternalBodyType(const box2DBodyType eType) +{ + switch (eType) + { + default: + case BOX2D_STATIC_BODY: + return b2_staticBody; + case BOX2D_KINEMATIC_BODY: + return b2_kinematicBody; + case BOX2D_DYNAMIC_BODY: + return b2_dynamicBody; + } +} + +box2DBodyType getBox2DLOBodyType(const b2BodyType eType) +{ + switch (eType) + { + default: + case b2_staticBody: + return BOX2D_STATIC_BODY; + case b2_kinematicBody: + return BOX2D_KINEMATIC_BODY; + case b2_dynamicBody: + return BOX2D_DYNAMIC_BODY; + } +} + +b2Vec2 convertB2DPointToBox2DVec2(const basegfx::B2DPoint& aPoint, const double fScaleFactor) +{ + return { static_cast<float>(aPoint.getX() * fScaleFactor), + static_cast<float>(aPoint.getY() * -fScaleFactor) }; +} + +// expects rTriangleVector to have coordinates relative to the shape's bounding box center +void addTriangleVectorToBody(const basegfx::triangulator::B2DTriangleVector& rTriangleVector, + b2Body* aBody, const float fDensity, const float fFriction, + const float fRestitution, const double fScaleFactor) +{ + for (const basegfx::triangulator::B2DTriangle& aTriangle : rTriangleVector) + { + b2FixtureDef aFixture; + b2PolygonShape aPolygonShape; + b2Vec2 aTriangleVertices[3] + = { convertB2DPointToBox2DVec2(aTriangle.getA(), fScaleFactor), + convertB2DPointToBox2DVec2(aTriangle.getB(), fScaleFactor), + convertB2DPointToBox2DVec2(aTriangle.getC(), fScaleFactor) }; + + bool bValidPointDistance = true; + + // check whether the triangle has degenerately close points + for (int nPointIndexA = 0; nPointIndexA < 3; nPointIndexA++) + { + for (int nPointIndexB = 0; nPointIndexB < 3; nPointIndexB++) + { + if (nPointIndexA == nPointIndexB) + continue; + + if (b2DistanceSquared(aTriangleVertices[nPointIndexA], + aTriangleVertices[nPointIndexB]) + < 0.003f) + { + bValidPointDistance = false; + } + } + } + + if (bValidPointDistance) + { + // create a fixture that represents the triangle + aPolygonShape.Set(aTriangleVertices, 3); + aFixture.shape = &aPolygonShape; + aFixture.density = fDensity; + aFixture.friction = fFriction; + aFixture.restitution = fRestitution; + aBody->CreateFixture(&aFixture); + } + } +} + +// expects rPolygon to have coordinates relative to it's center +void addEdgeShapeToBody(const basegfx::B2DPolygon& rPolygon, b2Body* aBody, const float fDensity, + const float fFriction, const float fRestitution, const double fScaleFactor) +{ + // make sure there's no bezier curves on the polygon + assert(!rPolygon.areControlPointsUsed()); + basegfx::B2DPolygon aPolygon = basegfx::utils::removeNeutralPoints(rPolygon); + + // value that somewhat defines half width of the quadrilateral + // that will be representing edge segment in the box2d world + const float fHalfWidth = 0.1f; + bool bHasPreviousQuadrilateralEdge = false; + b2Vec2 aQuadrilateralVertices[4]; + + for (sal_uInt32 nIndex = 0; nIndex < aPolygon.count(); nIndex++) + { + b2FixtureDef aFixture; + b2PolygonShape aPolygonShape; + + basegfx::B2DPoint aPointA; + basegfx::B2DPoint aPointB; + if (nIndex != 0) + { + // get two adjacent points to create an edge out of + aPointA = aPolygon.getB2DPoint(nIndex - 1); + aPointB = aPolygon.getB2DPoint(nIndex); + } + else if (aPolygon.isClosed()) + { + // start by connecting the last point to the first one + aPointA = aPolygon.getB2DPoint(aPolygon.count() - 1); + aPointB = aPolygon.getB2DPoint(nIndex); + } + else // the polygon isn't closed, won't connect last and first points + { + continue; + } + + // create a vector that represents the direction of the edge + // and make it a unit vector + b2Vec2 aEdgeUnitVec(convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + - convertB2DPointToBox2DVec2(aPointA, fScaleFactor)); + aEdgeUnitVec.Normalize(); + + // create a unit vector that represents Normal of the edge + b2Vec2 aEdgeNormal(-aEdgeUnitVec.y, aEdgeUnitVec.x); + + // if there was an edge previously created it should just connect + // using it's ending points so that there are no empty spots + // between edge segments, if not use wherever aPointA is at + if (!bHasPreviousQuadrilateralEdge) + { + // the point is translated along the edge normal both directions by + // fHalfWidth to create a quadrilateral edge + aQuadrilateralVertices[0] + = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + fHalfWidth * aEdgeNormal; + aQuadrilateralVertices[1] + = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + -fHalfWidth * aEdgeNormal; + bHasPreviousQuadrilateralEdge = true; + } + aQuadrilateralVertices[2] + = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + fHalfWidth * aEdgeNormal; + aQuadrilateralVertices[3] + = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + -fHalfWidth * aEdgeNormal; + + // check whether the edge would have degenerately close points + bool bValidPointDistance + = b2DistanceSquared(aQuadrilateralVertices[0], aQuadrilateralVertices[2]) > 0.003f; + + if (bValidPointDistance) + { + // create a quadrilateral shaped fixture to represent the edge + aPolygonShape.Set(aQuadrilateralVertices, 4); + aFixture.shape = &aPolygonShape; + aFixture.density = fDensity; + aFixture.friction = fFriction; + aFixture.restitution = fRestitution; + aBody->CreateFixture(&aFixture); + + // prepare the quadrilateral edge for next connection + aQuadrilateralVertices[0] = aQuadrilateralVertices[2]; + aQuadrilateralVertices[1] = aQuadrilateralVertices[3]; + } + } +} + +void addEdgeShapeToBody(const basegfx::B2DPolyPolygon& rPolyPolygon, b2Body* aBody, + const float fDensity, const float fFriction, const float fRestitution, + const double fScaleFactor) +{ + for (const basegfx::B2DPolygon& rPolygon : rPolyPolygon) + { + addEdgeShapeToBody(rPolygon, aBody, fDensity, fFriction, fRestitution, fScaleFactor); + } +} +} + +box2DWorld::box2DWorld(const ::basegfx::B2DVector& rSlideSize) + : mpBox2DWorld() + , mfScaleFactor(calculateScaleFactor(rSlideSize)) + , mbShapesInitialized(false) + , mbHasWorldStepper(false) + , mbAlreadyStepped(false) + , mnPhysicsAnimationCounter(0) + , mpXShapeToBodyMap() + , maShapeParallelUpdateQueue() +{ +} + +box2DWorld::~box2DWorld() = default; + +bool box2DWorld::initiateWorld(const ::basegfx::B2DVector& rSlideSize) +{ + if (!mpBox2DWorld) + { + mpBox2DWorld = std::make_unique<b2World>(b2Vec2(0.0f, -30.0f)); + createStaticFrameAroundSlide(rSlideSize); + return false; + } + else + { + return true; + } +} + +void box2DWorld::createStaticFrameAroundSlide(const ::basegfx::B2DVector& rSlideSize) +{ + assert(mpBox2DWorld); + + float fWidth = static_cast<float>(rSlideSize.getX() * mfScaleFactor); + float fHeight = static_cast<float>(rSlideSize.getY() * mfScaleFactor); + + // static body for creating the frame around the slide + b2BodyDef aBodyDef; + aBodyDef.type = b2_staticBody; + aBodyDef.position.Set(0, 0); + + // not going to be stored anywhere, will live + // as long as the Box2DWorld does + b2Body* pStaticBody = mpBox2DWorld->CreateBody(&aBodyDef); + + // create an edge loop that represents slide frame + b2Vec2 aEdgePoints[4]; + aEdgePoints[0].Set(0, 0); + aEdgePoints[1].Set(0, -fHeight); + aEdgePoints[2].Set(fWidth, -fHeight); + aEdgePoints[3].Set(fWidth, 0); + + b2ChainShape aEdgesChainShape; + aEdgesChainShape.CreateLoop(aEdgePoints, 4); + + // create the fixture for the shape + b2FixtureDef aFixtureDef; + aFixtureDef.shape = &aEdgesChainShape; + pStaticBody->CreateFixture(&aFixtureDef); +} + +void box2DWorld::setShapePosition(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const basegfx::B2DPoint& rOutPos) +{ + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setPosition(rOutPos); +} + +void box2DWorld::setShapePositionByLinearVelocity( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const basegfx::B2DPoint& rOutPos, const double fPassedTime) +{ + assert(mpBox2DWorld); + if (fPassedTime > 0) // this only makes sense if there was an advance in time + { + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setPositionByLinearVelocity(rOutPos, fPassedTime); + } +} + +void box2DWorld::setShapeLinearVelocity( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const basegfx::B2DVector& rVelocity) +{ + assert(mpBox2DWorld); + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setLinearVelocity(rVelocity); +} + +void box2DWorld::setShapeAngle(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const double fAngle) +{ + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setAngle(fAngle); +} + +void box2DWorld::setShapeAngleByAngularVelocity( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, const double fAngle, + const double fPassedTime) +{ + assert(mpBox2DWorld); + if (fPassedTime > 0) // this only makes sense if there was an advance in time + { + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setAngleByAngularVelocity(fAngle, fPassedTime); + } +} + +void box2DWorld::setShapeAngularVelocity( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const double fAngularVelocity) +{ + assert(mpBox2DWorld); + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setAngularVelocity(fAngularVelocity); +} + +void box2DWorld::setShapeCollision( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, bool bCanCollide) +{ + assert(mpBox2DWorld); + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setCollision(bCanCollide); +} + +void box2DWorld::processUpdateQueue(const double fPassedTime) +{ + while (!maShapeParallelUpdateQueue.empty()) + { + Box2DDynamicUpdateInformation& aQueueElement = maShapeParallelUpdateQueue.front(); + + if (aQueueElement.mnDelayForSteps > 0) + { + // it was queued as a delayed action, skip it, don't pop + aQueueElement.mnDelayForSteps--; + } + else + { + switch (aQueueElement.meUpdateType) + { + default: + case BOX2D_UPDATE_POSITION_CHANGE: + setShapePositionByLinearVelocity(aQueueElement.mxShape, + aQueueElement.maPosition, fPassedTime); + break; + case BOX2D_UPDATE_POSITION: + setShapePosition(aQueueElement.mxShape, aQueueElement.maPosition); + break; + case BOX2D_UPDATE_ANGLE: + setShapeAngleByAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngle, + fPassedTime); + break; + case BOX2D_UPDATE_SIZE: + break; + case BOX2D_UPDATE_VISIBILITY: + setShapeCollision(aQueueElement.mxShape, aQueueElement.mbVisibility); + break; + case BOX2D_UPDATE_LINEAR_VELOCITY: + setShapeLinearVelocity(aQueueElement.mxShape, aQueueElement.maVelocity); + break; + case BOX2D_UPDATE_ANGULAR_VELOCITY: + setShapeAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngularVelocity); + } + maShapeParallelUpdateQueue.pop(); + } + } +} + +void box2DWorld::initiateAllShapesAsStaticBodies( + const slideshow::internal::ShapeManagerSharedPtr& pShapeManager) +{ + assert(mpBox2DWorld); + + mbShapesInitialized = true; + auto aXShapeToShapeMap = pShapeManager->getXShapeToShapeMap(); + + std::unordered_map<css::uno::Reference<css::drawing::XShape>, bool> aXShapeBelongsToAGroup; + + // iterate over the shapes in the current slide and flag them if they belong to a group + // will flag the only ones that are belong to a group since std::unordered_map operator[] + // defaults the value to false if the key doesn't have a corresponding value + for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++) + { + slideshow::internal::ShapeSharedPtr pShape = aIt->second; + if (pShape->isForeground()) + { + SdrObject* pTemp = SdrObject::getSdrObjectFromXShape(pShape->getXShape()); + if (pTemp && pTemp->IsGroupObject()) + { + // if it is a group object iterate over its children and flag them + SdrObjList* aObjList = pTemp->GetSubList(); + const size_t nObjCount(aObjList->GetObjCount()); + + for (size_t nObjIndex = 0; nObjIndex < nObjCount; ++nObjIndex) + { + SdrObject* pGroupMember(aObjList->GetObj(nObjIndex)); + aXShapeBelongsToAGroup.insert( + std::make_pair(GetXShapeForSdrObject(pGroupMember), true)); + } + } + } + } + + // iterate over shapes in the current slide + for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++) + { + slideshow::internal::ShapeSharedPtr pShape = aIt->second; + // only create static bodies for the shapes that do not belong to a group + // groups themselves will have one body that represents the whole shape + // collection + if (pShape->isForeground() && !aXShapeBelongsToAGroup[pShape->getXShape()]) + { + Box2DBodySharedPtr pBox2DBody = createStaticBody(pShape); + + mpXShapeToBodyMap.insert(std::make_pair(pShape->getXShape(), pBox2DBody)); + if (!pShape->isVisible()) + { + // if the shape isn't visible, queue an update for it + queueShapeVisibilityUpdate(pShape->getXShape(), false); + } + } + } +} + +bool box2DWorld::hasWorldStepper() const { return mbHasWorldStepper; } + +void box2DWorld::setHasWorldStepper(const bool bHasWorldStepper) +{ + mbHasWorldStepper = bHasWorldStepper; +} + +void box2DWorld::queueDynamicPositionUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const basegfx::B2DPoint& rOutPos) +{ + Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION_CHANGE }; + aQueueElement.maPosition = rOutPos; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueLinearVelocityUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const basegfx::B2DVector& rVelocity, const int nDelayForSteps) +{ + Box2DDynamicUpdateInformation aQueueElement + = { xShape, {}, BOX2D_UPDATE_LINEAR_VELOCITY, nDelayForSteps }; + aQueueElement.maVelocity = rVelocity; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueDynamicRotationUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, const double fAngle) +{ + Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_ANGLE }; + aQueueElement.mfAngle = fAngle; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueAngularVelocityUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const double fAngularVelocity, const int nDelayForSteps) +{ + Box2DDynamicUpdateInformation aQueueElement + = { xShape, {}, BOX2D_UPDATE_ANGULAR_VELOCITY, nDelayForSteps }; + aQueueElement.mfAngularVelocity = fAngularVelocity; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueShapeVisibilityUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, const bool bVisibility) +{ + Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_VISIBILITY }; + aQueueElement.mbVisibility = bVisibility; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueShapePositionUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const basegfx::B2DPoint& rOutPos) +{ + Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION }; + aQueueElement.maPosition = rOutPos; + maShapeParallelUpdateQueue.push(aQueueElement); +} + +void box2DWorld::queueShapePathAnimationUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, const bool bIsFirstUpdate) +{ + // Workaround for PathAnimations since they do not have their own AttributeType + // - using PosX makes it register a DynamicPositionUpdate - + queueShapeAnimationUpdate(xShape, pAttrLayer, slideshow::internal::AttributeType::PosX, + bIsFirstUpdate); +} + +void box2DWorld::queueShapeAnimationUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, + const slideshow::internal::AttributeType eAttrType, const bool bIsFirstUpdate) +{ + switch (eAttrType) + { + case slideshow::internal::AttributeType::Visibility: + queueShapeVisibilityUpdate(xShape, pAttrLayer->getVisibility()); + return; + case slideshow::internal::AttributeType::Rotate: + queueDynamicRotationUpdate(xShape, pAttrLayer->getRotationAngle()); + return; + case slideshow::internal::AttributeType::PosX: + case slideshow::internal::AttributeType::PosY: + if (bIsFirstUpdate) // if it is the first update shape should _teleport_ to the position + queueShapePositionUpdate(xShape, { pAttrLayer->getPosX(), pAttrLayer->getPosY() }); + else + queueDynamicPositionUpdate(xShape, + { pAttrLayer->getPosX(), pAttrLayer->getPosY() }); + return; + default: + return; + } +} + +void box2DWorld::queueShapeAnimationEndUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const slideshow::internal::AttributeType eAttrType) +{ + switch (eAttrType) + { + // end updates that change the velocity are delayed for a step + // since we do not want them to override the last position/angle + case slideshow::internal::AttributeType::Rotate: + queueAngularVelocityUpdate(xShape, 0.0, 1); + return; + case slideshow::internal::AttributeType::PosX: + case slideshow::internal::AttributeType::PosY: + queueLinearVelocityUpdate(xShape, { 0, 0 }, 1); + return; + default: + return; + } +} + +void box2DWorld::alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape) +{ + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(pShape->getXShape())->second; + // since the animation ended make the body static + makeBodyStatic(pBox2DBody); + pBox2DBody->setRestitution(fDefaultStaticBodyBounciness); + if (--mnPhysicsAnimationCounter == 0) + { + // if there are no more physics animation effects going on clean up + maShapeParallelUpdateQueue = {}; + mbShapesInitialized = false; + // clearing the map will make the box2d bodies get + // destroyed if there's nothing else that owns them + mpXShapeToBodyMap.clear(); + } + else + { + // the physics animation that will take over the lock after this one + // shouldn't step the world for an update cycle - since it was already + // stepped. + mbAlreadyStepped = true; + } +} + +void box2DWorld::alertPhysicsAnimationStart( + const ::basegfx::B2DVector& rSlideSize, + const slideshow::internal::ShapeManagerSharedPtr& pShapeManager) +{ + if (!mpBox2DWorld) + initiateWorld(rSlideSize); + + if (!mbShapesInitialized) + initiateAllShapesAsStaticBodies(pShapeManager); + + mnPhysicsAnimationCounter++; +} + +void box2DWorld::step(const float fTimeStep, const int nVelocityIterations, + const int nPositionIterations) +{ + assert(mpBox2DWorld); + mpBox2DWorld->Step(fTimeStep, nVelocityIterations, nPositionIterations); +} + +double box2DWorld::stepAmount(const double fPassedTime, const float fTimeStep, + const int nVelocityIterations, const int nPositionIterations) +{ + assert(mpBox2DWorld); + + unsigned int nStepAmount = static_cast<unsigned int>(std::round(fPassedTime / fTimeStep)); + // find the actual time that will be stepped through so + // that the updates can be processed using that value + double fTimeSteppedThrough = fTimeStep * nStepAmount; + + // do the updates required to simulate other animation effects going in parallel + processUpdateQueue(fTimeSteppedThrough); + + if (!mbAlreadyStepped) + { + for (unsigned int nStepCounter = 0; nStepCounter < nStepAmount; nStepCounter++) + { + step(fTimeStep, nVelocityIterations, nPositionIterations); + } + } + else + { + // just got the step lock from another physics animation + // so skipping stepping the world for an update cycle + mbAlreadyStepped = false; + } + + return fTimeSteppedThrough; +} + +bool box2DWorld::shapesInitialized() { return mbShapesInitialized; } + +bool box2DWorld::isInitialized() const +{ + if (mpBox2DWorld) + return true; + else + return false; +} + +Box2DBodySharedPtr +box2DWorld::makeShapeDynamic(const css::uno::Reference<css::drawing::XShape>& xShape, + const basegfx::B2DVector& rStartVelocity, const double fDensity, + const double fBounciness) +{ + assert(mpBox2DWorld); + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second; + pBox2DBody->setDensityAndRestitution(fDensity, fBounciness); + queueLinearVelocityUpdate(xShape, rStartVelocity, 1); + return makeBodyDynamic(pBox2DBody); +} + +Box2DBodySharedPtr makeBodyDynamic(const Box2DBodySharedPtr& pBox2DBody) +{ + if (pBox2DBody->getType() != BOX2D_DYNAMIC_BODY) + { + pBox2DBody->setType(BOX2D_DYNAMIC_BODY); + } + return pBox2DBody; +} + +Box2DBodySharedPtr box2DWorld::makeShapeStatic(const slideshow::internal::ShapeSharedPtr& pShape) +{ + assert(mpBox2DWorld); + Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(pShape->getXShape())->second; + return makeBodyStatic(pBox2DBody); +} + +Box2DBodySharedPtr makeBodyStatic(const Box2DBodySharedPtr& pBox2DBody) +{ + if (pBox2DBody->getType() != BOX2D_STATIC_BODY) + { + pBox2DBody->setType(BOX2D_STATIC_BODY); + } + return pBox2DBody; +} + +Box2DBodySharedPtr box2DWorld::createStaticBody(const slideshow::internal::ShapeSharedPtr& rShape, + const float fDensity, const float fFriction) +{ + assert(mpBox2DWorld); + + ::basegfx::B2DRectangle aShapeBounds = rShape->getBounds(); + + b2BodyDef aBodyDef; + aBodyDef.type = b2_staticBody; + aBodyDef.position = convertB2DPointToBox2DVec2(aShapeBounds.getCenter(), mfScaleFactor); + + slideshow::internal::ShapeAttributeLayerSharedPtr pShapeAttributeLayer + = static_cast<slideshow::internal::AttributableShape*>(rShape.get()) + ->getTopmostAttributeLayer(); + if (pShapeAttributeLayer && pShapeAttributeLayer->isRotationAngleValid()) + { + // if the shape's rotation value was altered by another animation effect set it. + aBodyDef.angle = ::basegfx::deg2rad(-pShapeAttributeLayer->getRotationAngle()); + } + + // create a shared pointer with a destructor so that the body will be properly destroyed + std::shared_ptr<b2Body> pBody(mpBox2DWorld->CreateBody(&aBodyDef), [](b2Body* pB2Body) { + pB2Body->GetWorld()->DestroyBody(pB2Body); + }); + + SdrObject* pSdrObject = SdrObject::getSdrObjectFromXShape(rShape->getXShape()); + + rtl::OUString aShapeType = rShape->getXShape()->getShapeType(); + + basegfx::B2DPolyPolygon aPolyPolygon; + // workaround: + // TakeXorPoly() doesn't return beziers for CustomShapes and we want the beziers + // so that we can decide the complexity of the polygons generated from them + if (aShapeType == "com.sun.star.drawing.CustomShape") + { + aPolyPolygon = static_cast<SdrObjCustomShape*>(pSdrObject)->GetLineGeometry(true); + } + else + { + aPolyPolygon = pSdrObject->TakeXorPoly(); + } + + // make beziers into polygons, using a high degree angle as fAngleBound in + // adaptiveSubdivideByAngle reduces complexity of the resulting polygon shapes + aPolyPolygon = aPolyPolygon.areControlPointsUsed() + ? basegfx::utils::adaptiveSubdivideByAngle(aPolyPolygon, 20) + : aPolyPolygon; + aPolyPolygon.removeDoublePoints(); + + // make polygon coordinates relative to the center of the shape instead of top left of the slide + // since box2d shapes are expressed this way + aPolyPolygon + = basegfx::utils::distort(aPolyPolygon, aPolyPolygon.getB2DRange(), + { -aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 }, + { aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 }, + { -aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 }, + { aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 }); + + if (pSdrObject->IsClosedObj() && !pSdrObject->IsEdgeObj() && pSdrObject->HasFillStyle()) + { + basegfx::triangulator::B2DTriangleVector aTriangleVector; + // iterate over the polygons of the shape and create representations for them + for (const auto& rPolygon : std::as_const(aPolyPolygon)) + { + // if the polygon is closed it will be represented by triangles + if (rPolygon.isClosed()) + { + basegfx::triangulator::B2DTriangleVector aTempTriangleVector( + basegfx::triangulator::triangulate(rPolygon)); + aTriangleVector.insert(aTriangleVector.end(), aTempTriangleVector.begin(), + aTempTriangleVector.end()); + } + else // otherwise it will be an edge representation (example: smile line of the smiley shape) + { + addEdgeShapeToBody(rPolygon, pBody.get(), fDensity, fFriction, + static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor); + } + } + addTriangleVectorToBody(aTriangleVector, pBody.get(), fDensity, fFriction, + static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor); + } + else + { + addEdgeShapeToBody(aPolyPolygon, pBody.get(), fDensity, fFriction, + static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor); + } + + return std::make_shared<box2DBody>(pBody, mfScaleFactor); +} + +box2DBody::box2DBody(std::shared_ptr<b2Body> pBox2DBody, double fScaleFactor) + : mpBox2DBody(pBox2DBody) + , mfScaleFactor(fScaleFactor) +{ +} + +::basegfx::B2DPoint box2DBody::getPosition() const +{ + b2Vec2 aPosition = mpBox2DBody->GetPosition(); + double fX = static_cast<double>(aPosition.x) / mfScaleFactor; + double fY = static_cast<double>(aPosition.y) / -mfScaleFactor; + return ::basegfx::B2DPoint(fX, fY); +} + +void box2DBody::setPosition(const basegfx::B2DPoint& rPos) +{ + mpBox2DBody->SetTransform(convertB2DPointToBox2DVec2(rPos, mfScaleFactor), + mpBox2DBody->GetAngle()); +} + +void box2DBody::setPositionByLinearVelocity(const basegfx::B2DPoint& rDesiredPos, + const double fPassedTime) +{ + // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity + if (mpBox2DBody->GetType() != b2_kinematicBody) + mpBox2DBody->SetType(b2_kinematicBody); + + ::basegfx::B2DPoint aCurrentPos = getPosition(); + // calculate the velocity needed to reach the rDesiredPos in the given time frame + ::basegfx::B2DVector aVelocity = (rDesiredPos - aCurrentPos) / fPassedTime; + + setLinearVelocity(aVelocity); +} + +void box2DBody::setAngleByAngularVelocity(const double fDesiredAngle, const double fPassedTime) +{ + // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity + if (mpBox2DBody->GetType() != b2_kinematicBody) + mpBox2DBody->SetType(b2_kinematicBody); + + double fDeltaAngle = fDesiredAngle - getAngle(); + + // temporary hack for repeating animation effects + while (fDeltaAngle > 180 + || fDeltaAngle < -180) // if it is bigger than 180 opposite rotation is actually closer + fDeltaAngle += fDeltaAngle > 0 ? -360 : +360; + + double fAngularVelocity = fDeltaAngle / fPassedTime; + setAngularVelocity(fAngularVelocity); +} + +void box2DBody::setLinearVelocity(const ::basegfx::B2DVector& rVelocity) +{ + b2Vec2 aVelocity = { static_cast<float>(rVelocity.getX() * mfScaleFactor), + static_cast<float>(rVelocity.getY() * -mfScaleFactor) }; + mpBox2DBody->SetLinearVelocity(aVelocity); +} + +void box2DBody::setAngularVelocity(const double fAngularVelocity) +{ + float fBox2DAngularVelocity = static_cast<float>(basegfx::deg2rad(-fAngularVelocity)); + mpBox2DBody->SetAngularVelocity(fBox2DAngularVelocity); +} + +void box2DBody::setCollision(const bool bCanCollide) +{ + // collision have to be set for each fixture of the body individually + for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture; + pFixture = pFixture->GetNext()) + { + b2Filter aFilter = pFixture->GetFilterData(); + // 0xFFFF means collides with everything + // 0x0000 means collides with nothing + aFilter.maskBits = bCanCollide ? 0xFFFF : 0x0000; + pFixture->SetFilterData(aFilter); + } +} + +double box2DBody::getAngle() const +{ + double fAngle = static_cast<double>(mpBox2DBody->GetAngle()); + return ::basegfx::rad2deg(-fAngle); +} + +void box2DBody::setAngle(const double fAngle) +{ + mpBox2DBody->SetTransform(mpBox2DBody->GetPosition(), ::basegfx::deg2rad(-fAngle)); +} + +void box2DBody::setDensityAndRestitution(const double fDensity, const double fRestitution) +{ + // density and restitution have to be set for each fixture of the body individually + for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture; + pFixture = pFixture->GetNext()) + { + pFixture->SetDensity(static_cast<float>(fDensity)); + pFixture->SetRestitution(static_cast<float>(fRestitution)); + } + // without resetting the massdata of the body, density change won't take effect + mpBox2DBody->ResetMassData(); +} + +void box2DBody::setRestitution(const double fRestitution) +{ + for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture; + pFixture = pFixture->GetNext()) + { + pFixture->SetRestitution(static_cast<float>(fRestitution)); + } +} + +void box2DBody::setType(box2DBodyType eType) +{ + mpBox2DBody->SetType(getBox2DInternalBodyType(eType)); +} + +box2DBodyType box2DBody::getType() const { return getBox2DLOBodyType(mpBox2DBody->GetType()); } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/slideshow/source/engine/color.cxx b/slideshow/source/engine/color.cxx new file mode 100644 index 000000000..1109d4e68 --- /dev/null +++ b/slideshow/source/engine/color.cxx @@ -0,0 +1,348 @@ +/* -*- 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 <hslcolor.hxx> +#include <rgbcolor.hxx> + +#include <basegfx/numeric/ftools.hxx> +#include <rtl/math.hxx> + +#include <cmath> +#include <algorithm> + + +namespace slideshow::internal +{ + namespace + { + // helper functions + // ================ + + double getMagic( double nLuminance, double nSaturation ) + { + if( nLuminance <= 0.5 ) + return nLuminance*(1.0 + nSaturation); + else + return nLuminance + nSaturation - nLuminance*nSaturation; + } + + HSLColor::HSLTriple rgb2hsl( double nRed, double nGreen, double nBlue ) + { + // r,g,b in [0,1], h in [0,360] and s,l in [0,1] + HSLColor::HSLTriple aRes; + + const double nMax( ::std::max(nRed,::std::max(nGreen, nBlue)) ); + const double nMin( ::std::min(nRed,::std::min(nGreen, nBlue)) ); + + const double nDelta( nMax - nMin ); + + aRes.mnLuminance = (nMax + nMin) / 2.0; + + if( ::basegfx::fTools::equalZero( nDelta ) ) + { + aRes.mnSaturation = 0.0; + + // hue undefined (achromatic case) + aRes.mnHue = 0.0; + } + else + { + aRes.mnSaturation = aRes.mnLuminance > 0.5 ? + nDelta/(2.0-nMax-nMin) : + nDelta/(nMax + nMin); + + if( rtl::math::approxEqual(nRed, nMax) ) + aRes.mnHue = (nGreen - nBlue)/nDelta; + else if( rtl::math::approxEqual(nGreen, nMax) ) + aRes.mnHue = 2.0 + (nBlue - nRed)/nDelta; + else if( rtl::math::approxEqual(nBlue, nMax) ) + aRes.mnHue = 4.0 + (nRed - nGreen)/nDelta; + + aRes.mnHue *= 60.0; + + if( aRes.mnHue < 0.0 ) + aRes.mnHue += 360.0; + } + + return aRes; + } + + double hsl2rgbHelper( double nValue1, double nValue2, double nHue ) + { + // clamp hue to [0,360] + nHue = fmod( nHue, 360.0 ); + + // cope with wrap-arounds + if( nHue < 0.0 ) + nHue += 360.0; + + if( nHue < 60.0 ) + return nValue1 + (nValue2 - nValue1)*nHue/60.0; + else if( nHue < 180.0 ) + return nValue2; + else if( nHue < 240.0 ) + return nValue1 + (nValue2 - nValue1)*(240.0 - nHue)/60.0; + else + return nValue1; + } + + RGBColor::RGBTriple hsl2rgb( double nHue, double nSaturation, double nLuminance ) + { + if( ::basegfx::fTools::equalZero( nSaturation ) ) + return RGBColor::RGBTriple(0.0, 0.0, nLuminance ); + + const double nVal1( getMagic(nLuminance, nSaturation) ); + const double nVal2( 2.0*nLuminance - nVal1 ); + + RGBColor::RGBTriple aRes; + + aRes.mnRed = hsl2rgbHelper( nVal2, + nVal1, + nHue + 120.0 ); + aRes.mnGreen = hsl2rgbHelper( nVal2, + nVal1, + nHue ); + aRes.mnBlue = hsl2rgbHelper( nVal2, + nVal1, + nHue - 120.0 ); + + return aRes; + } + + /// Truncate range of value to [0,1] + double truncateRangeStd( double nVal ) + { + return ::std::max( 0.0, + ::std::min( 1.0, + nVal ) ); + } + + /// Truncate range of value to [0,360] + double truncateRangeHue( double nVal ) + { + return ::std::max( 0.0, + ::std::min( 360.0, + nVal ) ); + } + + /// convert RGB color to sal_uInt8, truncate range appropriately before + sal_uInt8 colorToInt( double nCol ) + { + return static_cast< sal_uInt8 >( + ::basegfx::fround( truncateRangeStd( nCol ) * 255.0 ) ); + } + } + + + // HSLColor + + + HSLColor::HSLTriple::HSLTriple() : + mnHue(), + mnSaturation(), + mnLuminance() + { + } + + HSLColor::HSLTriple::HSLTriple( double nHue, double nSaturation, double nLuminance ) : + mnHue( nHue ), + mnSaturation( nSaturation ), + mnLuminance( nLuminance ) + { + } + + HSLColor::HSLColor() : + maHSLTriple( 0.0, 0.0, 0.0 ) + { + } + + HSLColor::HSLColor( double nHue, double nSaturation, double nLuminance ) : + maHSLTriple( nHue, nSaturation, nLuminance ) + { + } + + HSLColor::HSLColor( const RGBColor& rColor ) : + maHSLTriple( rgb2hsl( truncateRangeStd( rColor.getRed() ), + truncateRangeStd( rColor.getGreen() ), + truncateRangeStd( rColor.getBlue() ) ) ) + { + } + + + bool operator==( const HSLColor& rLHS, const HSLColor& rRHS ) + { + return ( rLHS.getHue() == rRHS.getHue() && + rLHS.getSaturation() == rRHS.getSaturation() && + rLHS.getLuminance() == rRHS.getLuminance() ); + } + + bool operator!=( const HSLColor& rLHS, const HSLColor& rRHS ) + { + return !( rLHS == rRHS ); + } + + HSLColor operator+( const HSLColor& rLHS, const HSLColor& rRHS ) + { + return HSLColor( rLHS.getHue() + rRHS.getHue(), + rLHS.getSaturation() + rRHS.getSaturation(), + rLHS.getLuminance() + rRHS.getLuminance() ); + } + + HSLColor operator*( double nFactor, const HSLColor& rRHS ) + { + return HSLColor( nFactor * rRHS.getHue(), + nFactor * rRHS.getSaturation(), + nFactor * rRHS.getLuminance() ); + } + + HSLColor interpolate( const HSLColor& rFrom, const HSLColor& rTo, double t, bool bCCW ) + { + const double nFromHue( rFrom.getHue() ); + const double nToHue ( rTo.getHue() ); + + double nHue=0.0; + + if( nFromHue <= nToHue && !bCCW ) + { + // interpolate hue clockwise. That is, hue starts at + // high values and ends at low ones. Therefore, we + // must 'cross' the 360 degrees and start at low + // values again (imagine the hues to lie on the + // circle, where values above 360 degrees are mapped + // back to [0,360)). + nHue = (1.0-t)*(nFromHue + 360.0) + t*nToHue; + } + else if( nFromHue > nToHue && bCCW ) + { + // interpolate hue counter-clockwise. That is, hue + // starts at high values and ends at low + // ones. Therefore, we must 'cross' the 360 degrees + // and start at low values again (imagine the hues to + // lie on the circle, where values above 360 degrees + // are mapped back to [0,360)). + nHue = (1.0-t)*nFromHue + t*(nToHue + 360.0); + } + else + { + // interpolate hue counter-clockwise. That is, hue + // starts at low values and ends at high ones (imagine + // the hue value as degrees on a circle, with + // increasing values going counter-clockwise) + nHue = (1.0-t)*nFromHue + t*nToHue; + } + + return HSLColor( nHue, + (1.0-t)*rFrom.getSaturation() + t*rTo.getSaturation(), + (1.0-t)*rFrom.getLuminance() + t*rTo.getLuminance() ); + } + + + // RGBColor + + + RGBColor::RGBTriple::RGBTriple() : + mnRed(), + mnGreen(), + mnBlue() + { + } + + RGBColor::RGBTriple::RGBTriple( double nRed, double nGreen, double nBlue ) : + mnRed( nRed ), + mnGreen( nGreen ), + mnBlue( nBlue ) + { + } + + RGBColor::RGBColor() : + maRGBTriple( 0.0, 0.0, 0.0 ) + { + } + + RGBColor::RGBColor( ::cppcanvas::IntSRGBA nRGBColor ) : + maRGBTriple( ::cppcanvas::getRed( nRGBColor ) / 255.0, + ::cppcanvas::getGreen( nRGBColor ) / 255.0, + ::cppcanvas::getBlue( nRGBColor ) / 255.0 ) + { + } + + RGBColor::RGBColor( double nRed, double nGreen, double nBlue ) : + maRGBTriple( nRed, nGreen, nBlue ) + { + } + + RGBColor::RGBColor( const HSLColor& rColor ) : + maRGBTriple( hsl2rgb( truncateRangeHue( rColor.getHue() ), + truncateRangeStd( rColor.getSaturation() ), + truncateRangeStd( rColor.getLuminance() ) ) ) + { + } + + + ::cppcanvas::IntSRGBA RGBColor::getIntegerColor() const + { + return ::cppcanvas::makeColor( colorToInt( getRed() ), + colorToInt( getGreen() ), + colorToInt( getBlue() ), + 255 ); + } + + bool operator==( const RGBColor& rLHS, const RGBColor& rRHS ) + { + return ( rLHS.getRed() == rRHS.getRed() && + rLHS.getGreen() == rRHS.getGreen() && + rLHS.getBlue() == rRHS.getBlue() ); + } + + bool operator!=( const RGBColor& rLHS, const RGBColor& rRHS ) + { + return !( rLHS == rRHS ); + } + + RGBColor operator+( const RGBColor& rLHS, const RGBColor& rRHS ) + { + return RGBColor( rLHS.getRed() + rRHS.getRed(), + rLHS.getGreen() + rRHS.getGreen(), + rLHS.getBlue() + rRHS.getBlue() ); + } + + RGBColor operator*( const RGBColor& rLHS, const RGBColor& rRHS ) + { + return RGBColor( rLHS.getRed() * rRHS.getRed(), + rLHS.getGreen() * rRHS.getGreen(), + rLHS.getBlue() * rRHS.getBlue() ); + } + + RGBColor operator*( double nFactor, const RGBColor& rRHS ) + { + return RGBColor( nFactor * rRHS.getRed(), + nFactor * rRHS.getGreen(), + nFactor * rRHS.getBlue() ); + } + + RGBColor interpolate( const RGBColor& rFrom, const RGBColor& rTo, double t ) + { + return RGBColor( (1.0-t)*rFrom.getRed() + t*rTo.getRed(), + (1.0-t)*rFrom.getGreen() + t*rTo.getGreen(), + (1.0-t)*rFrom.getBlue() + t*rTo.getBlue() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/delayevent.cxx b/slideshow/source/engine/delayevent.cxx new file mode 100644 index 000000000..169012310 --- /dev/null +++ b/slideshow/source/engine/delayevent.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 <osl/diagnose.h> +#include <delayevent.hxx> + +namespace slideshow::internal { + +bool Delay::fire() +{ + OSL_ASSERT( isCharged() ); + if (isCharged()) { + mbWasFired = true; + maFunc(); + maFunc = nullptr; // early release of payload + } + return true; +} + +bool Delay::isCharged() const +{ + return !mbWasFired; +} + +double Delay::getActivationTime( double nCurrentTime ) const +{ + return nCurrentTime + mnTimeout; +} + +void Delay::dispose() +{ + // don't clear unconditionally, because it may currently be executed: + if (isCharged()) { + mbWasFired = true; + maFunc = nullptr; // release of payload + } +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/effectrewinder.cxx b/slideshow/source/engine/effectrewinder.cxx new file mode 100644 index 000000000..48a83d591 --- /dev/null +++ b/slideshow/source/engine/effectrewinder.cxx @@ -0,0 +1,404 @@ +/* -*- 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 "effectrewinder.hxx" +#include <eventqueue.hxx> +#include <usereventqueue.hxx> +#include <basecontainernode.hxx> +#include <delayevent.hxx> + +#include <com/sun/star/animations/Event.hpp> +#include <com/sun/star/animations/EventTrigger.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +using ::com::sun::star::uno::Reference; +using namespace ::com::sun::star; + +namespace slideshow::internal { + + +namespace { + +class RewinderEventHandler : public EventHandler +{ +public: + typedef ::std::function<bool ()> Action; + explicit RewinderEventHandler (const Action& rAction) : maAction(rAction) {} + +private: + const Action maAction; + virtual bool handleEvent() override { return maAction(); } +}; + + +class RewinderAnimationEventHandler : public AnimationEventHandler +{ +public: + typedef ::std::function<bool (const AnimationNodeSharedPtr& rpNode)> Action; + explicit RewinderAnimationEventHandler (const Action& rAction) : maAction(rAction) {} + +private: + const Action maAction; + virtual bool handleAnimationEvent (const AnimationNodeSharedPtr& rpNode) override + { return maAction(rpNode); } +}; + + +} // end of anonymous namespace + + +//----- EffectRewinder -------------------------------------------------------------- + +EffectRewinder::EffectRewinder ( + EventMultiplexer& rEventMultiplexer, + EventQueue& rEventQueue, + UserEventQueue& rUserEventQueue) + : mrEventMultiplexer(rEventMultiplexer), + mrEventQueue(rEventQueue), + mrUserEventQueue(rUserEventQueue), + mpSlideStartHandler(), + mpSlideEndHandler(), + mpAnimationStartHandler(), + mnMainSequenceEffectCount(0), + mpAsynchronousRewindEvent(), + mxCurrentAnimationRootNode(), + mxCurrentSlide(), + mbNonUserTriggeredMainSequenceEffectSeen(false), + mbHasAdvancedTimeSetting(false) +{ + initialize(); +} + + +void EffectRewinder::initialize() +{ + // Add some event handlers so that we are informed when + // a) an animation is started (we then check whether that belongs to a + // main sequence effect and if so, increase the respective counter), + // b,c) a slide was started or ended (in which case the effect counter + // is reset. + + mpAnimationStartHandler = + std::make_shared<RewinderAnimationEventHandler>( + [this]( const AnimationNodeSharedPtr& pNode) + { return this->notifyAnimationStart( pNode ); } ); + mrEventMultiplexer.addAnimationStartHandler(mpAnimationStartHandler); + + mpSlideStartHandler = + std::make_shared<RewinderEventHandler>( + [this]() { return this->resetEffectCount(); } ); + mrEventMultiplexer.addSlideStartHandler(mpSlideStartHandler); + + mpSlideEndHandler = + std::make_shared<RewinderEventHandler>( + [this]() { return this->resetEffectCount(); } ); + mrEventMultiplexer.addSlideEndHandler(mpSlideEndHandler); +} + + +EffectRewinder::~EffectRewinder() +{ + dispose(); +} + + +void EffectRewinder::dispose() +{ + if (mpAsynchronousRewindEvent) + { + mpAsynchronousRewindEvent->dispose(); + mpAsynchronousRewindEvent.reset(); + } + + if (mpAnimationStartHandler) + { + mrEventMultiplexer.removeAnimationStartHandler(mpAnimationStartHandler); + mpAnimationStartHandler.reset(); + } + + if (mpSlideStartHandler) + { + mrEventMultiplexer.removeSlideStartHandler(mpSlideStartHandler); + mpSlideStartHandler.reset(); + } + + if (mpSlideEndHandler) + { + mrEventMultiplexer.removeSlideEndHandler(mpSlideEndHandler); + mpSlideEndHandler.reset(); + } +} + + +void EffectRewinder::setRootAnimationNode ( + const uno::Reference<animations::XAnimationNode>& xRootNode) +{ + mxCurrentAnimationRootNode = xRootNode; +} + +void EffectRewinder::setCurrentSlide ( + const uno::Reference<drawing::XDrawPage>& xSlide) +{ + mxCurrentSlide = xSlide; + + // Check if the current slide has advance time setting or not + uno::Reference< beans::XPropertySet > xPropSet( mxCurrentSlide, uno::UNO_QUERY ); + sal_Int32 nChange(0); + + if( xPropSet.is()) + getPropertyValue( nChange, xPropSet, "Change"); + + mbHasAdvancedTimeSetting = nChange; +} + +bool EffectRewinder::rewind ( + const ::std::shared_ptr<ScreenUpdater::UpdateLock>& rpPaintLock, + const ::std::function<void ()>& rSlideRewindFunctor, + const ::std::function<void ()>& rPreviousSlideFunctor) +{ + mpPaintLock = rpPaintLock; + + // Do not allow nested rewinds. + if (mpAsynchronousRewindEvent) + { + OSL_ASSERT( ! mpAsynchronousRewindEvent); + return false; + } + + // Abort (and skip over the rest of) any currently active animation. + mrUserEventQueue.callSkipEffectEventHandler(); + + if (!mbHasAdvancedTimeSetting) + mrEventQueue.forceEmpty(); + + const int nSkipCount (mnMainSequenceEffectCount - 1); + if (nSkipCount < 0) + { + if ( ! rPreviousSlideFunctor) + { + OSL_ASSERT(rPreviousSlideFunctor); + return false; + } + + // No main sequence effects to rewind on the current slide. + // Go back to the previous slide. + mpAsynchronousRewindEvent = makeEvent( + ::std::bind( + &EffectRewinder::asynchronousRewindToPreviousSlide, + this, + rPreviousSlideFunctor), + "EffectRewinder::asynchronousRewindToPreviousSlide"); + } + else + { + // The actual rewinding is done asynchronously so that we can safely + // call other methods. + mpAsynchronousRewindEvent = makeEvent( + ::std::bind( + &EffectRewinder::asynchronousRewind, + this, + nSkipCount, + true, + rSlideRewindFunctor), + "EffectRewinder::asynchronousRewind"); + } + + if (mpAsynchronousRewindEvent) + mrEventQueue.addEvent(mpAsynchronousRewindEvent); + + return bool(mpAsynchronousRewindEvent); +} + + +void EffectRewinder::skipAllMainSequenceEffects() +{ + // Do not allow nested rewinds. + if (mpAsynchronousRewindEvent) + { + OSL_ASSERT(!mpAsynchronousRewindEvent); + return; + } + + const int nTotalMainSequenceEffectCount (countMainSequenceEffects()); + mpAsynchronousRewindEvent = makeEvent( + ::std::bind( + &EffectRewinder::asynchronousRewind, + this, + nTotalMainSequenceEffectCount, + false, + ::std::function<void ()>()), + "EffectRewinder::asynchronousRewind"); + mrEventQueue.addEvent(mpAsynchronousRewindEvent); +} + + +sal_Int32 EffectRewinder::countMainSequenceEffects() +{ + // Determine the number of main sequence effects. + sal_Int32 nMainSequenceNodeCount (0); + + ::std::queue<uno::Reference<animations::XAnimationNode> > aNodeQueue; + aNodeQueue.push(mxCurrentAnimationRootNode); + while ( ! aNodeQueue.empty()) + { + const uno::Reference<animations::XAnimationNode> xNode (aNodeQueue.front()); + aNodeQueue.pop(); + + // Does the current node belong to the main sequence? + if (xNode.is()) + { + animations::Event aEvent; + if (xNode->getBegin() >>= aEvent) + if (aEvent.Trigger == animations::EventTrigger::ON_NEXT) + ++nMainSequenceNodeCount; + } + + // If the current node is a container then prepare its children for investigation. + uno::Reference<container::XEnumerationAccess> xEnumerationAccess (xNode, uno::UNO_QUERY); + if (xEnumerationAccess.is()) + { + uno::Reference<container::XEnumeration> xEnumeration ( + xEnumerationAccess->createEnumeration()); + if (xEnumeration.is()) + while (xEnumeration->hasMoreElements()) + { + aNodeQueue.push( + uno::Reference<animations::XAnimationNode>( + xEnumeration->nextElement(), uno::UNO_QUERY)); + } + } + } + + return nMainSequenceNodeCount; +} + + +void EffectRewinder::skipSingleMainSequenceEffects() +{ + // This basically just starts the next effect and then skips over its + // animation. + mrEventMultiplexer.notifyNextEffect(); + mrEventQueue.forceEmpty(); + mrUserEventQueue.callSkipEffectEventHandler(); + mrEventQueue.forceEmpty(); +} + + +bool EffectRewinder::resetEffectCount() +{ + mnMainSequenceEffectCount = 0; + return false; +} + + +bool EffectRewinder::notifyAnimationStart (const AnimationNodeSharedPtr& rpNode) +{ + // This notification is only relevant for us when the rpNode belongs to + // the main sequence. + BaseNodeSharedPtr pBaseNode (::std::dynamic_pointer_cast<BaseNode>(rpNode)); + if ( ! pBaseNode) + return false; + + BaseContainerNodeSharedPtr pParent (pBaseNode->getParentNode()); + if ( ! (pParent && pParent->isMainSequenceRootNode())) + return false; + + // This notification is only relevant for us when the effect is user + // triggered. + bool bIsUserTriggered (false); + + Reference<animations::XAnimationNode> xNode (rpNode->getXAnimationNode()); + if (xNode.is()) + { + animations::Event aEvent; + if (xNode->getBegin() >>= aEvent) + bIsUserTriggered = (aEvent.Trigger == animations::EventTrigger::ON_NEXT); + } + + if (bIsUserTriggered) + ++mnMainSequenceEffectCount; + else + mbNonUserTriggeredMainSequenceEffectSeen = true; + + return false; +} + + +void EffectRewinder::asynchronousRewind ( + sal_Int32 nEffectCount, + const bool bRedisplayCurrentSlide, + const std::function<void ()>& rSlideRewindFunctor) +{ + OSL_ASSERT(mpAsynchronousRewindEvent); + + if (bRedisplayCurrentSlide) + { + mpPaintLock->Activate(); + // Re-display the current slide. + if (rSlideRewindFunctor) + rSlideRewindFunctor(); + mpAsynchronousRewindEvent = makeEvent( + ::std::bind( + &EffectRewinder::asynchronousRewind, + this, + nEffectCount, + false, + rSlideRewindFunctor), + "EffectRewinder::asynchronousRewind"); + mrEventQueue.addEvent(mpAsynchronousRewindEvent); + } + else + { + // Process initial events and skip any animations that are started + // when the slide is shown. + mbNonUserTriggeredMainSequenceEffectSeen = false; + + if (!mbHasAdvancedTimeSetting) + mrEventQueue.forceEmpty(); + + if (mbNonUserTriggeredMainSequenceEffectSeen) + { + mrUserEventQueue.callSkipEffectEventHandler(); + mrEventQueue.forceEmpty(); + } + + while (--nEffectCount >= 0) + skipSingleMainSequenceEffects(); + + mpAsynchronousRewindEvent.reset(); + mpPaintLock.reset(); + } +} + + +void EffectRewinder::asynchronousRewindToPreviousSlide ( + const ::std::function<void ()>& rSlideRewindFunctor) +{ + OSL_ASSERT(mpAsynchronousRewindEvent); + + mpAsynchronousRewindEvent.reset(); + rSlideRewindFunctor(); +} + + +} // end of namespace ::slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/effectrewinder.hxx b/slideshow/source/engine/effectrewinder.hxx new file mode 100644 index 000000000..093259785 --- /dev/null +++ b/slideshow/source/engine/effectrewinder.hxx @@ -0,0 +1,176 @@ +/* -*- 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_EFFECTREWINDER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_EFFECTREWINDER_HXX + +#include <animationnode.hxx> +#include <eventhandler.hxx> +#include <animationeventhandler.hxx> +#include <event.hxx> +#include <screenupdater.hxx> +#include <com/sun/star/drawing/XDrawPage.hpp> + +#include <functional> +#include <memory> + +namespace slideshow::internal { + +class EventMultiplexer; +class EventQueue; +class UserEventQueue; + +/** Rewind single effects of the main effect sequence. A rewind is + initiated by calling the Rewind() method. Part of the processing is + done asynchronously. Multiple EventQueue::update() calls may be + necessary to finish a rewind. + + Remember to call SetRootAnimationNode() when switching to a different + slide so that the EffectRewinder can determine the number of main + sequence effects. +*/ +class EffectRewinder +{ +public: + EffectRewinder ( + EventMultiplexer& rEventMultiplexer, + EventQueue& rEventQueue, + UserEventQueue& rUserEventQueue); + ~EffectRewinder(); + + /** Call Dispose() before the owner of an EffectRewinder object dies so + that the EffectRewinder can release all references to the owner. + + */ + void dispose(); + + /** Store the root node of the animation tree. It is used in + CountMainSequenceEffects() to count the number of main sequence + effects (or effect groups.) + */ + void setRootAnimationNode ( + const css::uno::Reference<css::animations::XAnimationNode>& xRootNode); + + /** Store the XDrawPage to reach specific slide properties. + */ + void setCurrentSlide ( + const css::uno::Reference<css::drawing::XDrawPage>& xSlide); + + /** Rewind one effect of the main effect sequence. When the current + slide has not effects or no main sequence effect has yet been played + then switch to the previous slide and replay all of its main + sequence effects. + The caller has to pass two functors that redisplay the current slide + or switch to the previous slide so that it does not have to expose + its internals to us. Only one of the two functors is called. + @param rpPaintLock + This paint lock is released after the whole asynchronous + process of rewinding the current effect is completed. It + prevents intermediate repaints that would show partial replay + of effects. + @param rSlideRewindFunctor + This functor is called when the current slide is to be + redisplayed. When it is called then the other functor is not + called. + @param rPreviousSlideFunctor + This functor is called to switch to the previous slide. When it + is called then the other functor is not called. + */ + bool rewind ( + const ::std::shared_ptr<ScreenUpdater::UpdateLock>& rpPaintLock, + const ::std::function<void ()>& rSlideRewindFunctor, + const ::std::function<void ()>& rPreviousSlideFunctor); + + /** Call this method after gotoPreviousEffect() triggered a slide change + to the previous slide. + */ + void skipAllMainSequenceEffects(); + +private: + EventMultiplexer& mrEventMultiplexer; + EventQueue& mrEventQueue; + UserEventQueue& mrUserEventQueue; + + EventHandlerSharedPtr mpSlideStartHandler; + EventHandlerSharedPtr mpSlideEndHandler; + AnimationEventHandlerSharedPtr mpAnimationStartHandler; + + /** The number off main sequence effects so far. + */ + sal_Int32 mnMainSequenceEffectCount; + + /** This is the currently scheduled event that executes the asynchronous + part of the effect rewinding. It is also used as flag that prevents + nested rewinds. + */ + EventSharedPtr mpAsynchronousRewindEvent; + + css::uno::Reference<css::animations::XAnimationNode> mxCurrentAnimationRootNode; + css::uno::Reference<css::drawing::XDrawPage> mxCurrentSlide; + ::std::shared_ptr<ScreenUpdater::UpdateLock> mpPaintLock; + + bool mbNonUserTriggeredMainSequenceEffectSeen; + bool mbHasAdvancedTimeSetting; // Slide has advanced time setting or not. + + void initialize(); + + bool resetEffectCount(); + /** Called by listeners when an animation (not necessarily of a main + sequence effect) starts. + */ + bool notifyAnimationStart (const AnimationNodeSharedPtr& rpNode); + + /** Count the number of effects (or effect groups) in the main effect + sequence. + */ + sal_Int32 countMainSequenceEffects(); + + /** Skip the next main sequence effect. + */ + void skipSingleMainSequenceEffects(); + + /** Rewind the last effect of the main effect sequence by replaying all + previous effects. + @param nEffectCount + The number of main sequence effects to replay. + @param bRedisplayCurrentSlide + When <TRUE/> then the current slide is redisplayed before the + effects are replayed. + @param rSlideRewindFunctor + This functor is used to redisplay the current slide. + */ + void asynchronousRewind ( + sal_Int32 nEffectCount, + const bool bRedisplayCurrentSlide, + const ::std::function<void ()>& rSlideRewindFunctor); + + /** Go to the previous slide and replay all of its main sequence effects + (or effect groups). + @param rPreviousSlideFunctor + This functor is used to go to the previous slide. + */ + void asynchronousRewindToPreviousSlide ( + const ::std::function<void ()>& rPreviousSlideFunctor); +}; + +} // end of namespace ::slideshow::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/eventmultiplexer.cxx b/slideshow/source/engine/eventmultiplexer.cxx new file mode 100644 index 000000000..2b53073c7 --- /dev/null +++ b/slideshow/source/engine/eventmultiplexer.cxx @@ -0,0 +1,1242 @@ +/* -*- 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 <rtl/ref.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/awt/XMouseMotionListener.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> + +#include <eventqueue.hxx> +#include <eventmultiplexer.hxx> +#include <listenercontainer.hxx> +#include <delayevent.hxx> +#include <unoview.hxx> +#include <unoviewcontainer.hxx> + +#include <functional> +#include <memory> +#include <algorithm> +#include <type_traits> +#include <utility> +#include <vector> + +using namespace ::com::sun::star; + +namespace +{ + // add operator== for weak_ptr, so we can use std::find over lists of them + struct ViewEventHandlerWeakPtrWrapper final { + slideshow::internal::ViewEventHandlerWeakPtr ptr; + + ViewEventHandlerWeakPtrWrapper(slideshow::internal::ViewEventHandlerWeakPtr thePtr): + ptr(std::move(thePtr)) {} + + bool operator ==(ViewEventHandlerWeakPtrWrapper const & other) const + { return ptr.lock().get() == other.ptr.lock().get(); } + }; +} + +// Needed by ImplViewHandlers; see the ListenerOperations<std::weak_ptr<ListenerTargetT>> partial +// specialization in slideshow/source/inc/listenercontainer.hxx: +template<> +struct slideshow::internal::ListenerOperations<ViewEventHandlerWeakPtrWrapper> +{ + template< typename ContainerT, + typename FuncT > + static bool notifySingleListener( ContainerT& rContainer, + FuncT func ) + { + for( const auto& rCurr : rContainer ) + { + std::shared_ptr<ViewEventHandler> pListener( rCurr.ptr.lock() ); + + if( pListener && func(pListener) ) + return true; + } + + return false; + } + + template< typename ContainerT, + typename FuncT > + static bool notifyAllListeners( ContainerT& rContainer, + FuncT func ) + { + bool bRet(false); + for( const auto& rCurr : rContainer ) + { + std::shared_ptr<ViewEventHandler> pListener( rCurr.ptr.lock() ); + + if( pListener.get() && + FunctionApply<typename ::std::invoke_result<FuncT, std::shared_ptr<ViewEventHandler> const&>::type, + std::shared_ptr<ViewEventHandler> >::apply(func,pListener) ) + { + bRet = true; + } + } + + return bRet; + } + template< typename ContainerT > + static void pruneListeners( ContainerT& rContainer, + size_t nSizeThreshold ) + { + if( rContainer.size() <= nSizeThreshold ) + return; + + ContainerT aAliveListeners; + aAliveListeners.reserve(rContainer.size()); + + for( const auto& rCurr : rContainer ) + { + if( !rCurr.ptr.expired() ) + aAliveListeners.push_back( rCurr ); + } + + std::swap( rContainer, aAliveListeners ); + } +}; + +namespace slideshow::internal { + +namespace { + +template <typename HandlerT> +class PrioritizedHandlerEntry +{ + typedef std::shared_ptr<HandlerT> HandlerSharedPtrT; + HandlerSharedPtrT mpHandler; + double mnPrio; + +public: + PrioritizedHandlerEntry( HandlerSharedPtrT const& pHandler, + double nPrio ) : + mpHandler(pHandler), + mnPrio(nPrio) + {} + + HandlerSharedPtrT const& getHandler() const { return mpHandler; } + + /// To sort according to priority + bool operator<( PrioritizedHandlerEntry const& rRHS ) const + { + // reversed order - high prioritized entries + // should be at the beginning of the queue + return mnPrio > rRHS.mnPrio; + } + + /// To permit std::remove in removeHandler template + bool operator==( PrioritizedHandlerEntry const& rRHS ) const + { + // ignore prio, for removal, only the handler ptr matters + return mpHandler == rRHS.mpHandler; + } +}; + +} + +typedef cppu::WeakComponentImplHelper< + awt::XMouseListener, + awt::XMouseMotionListener > Listener_UnoBase; + +namespace { + +/** Listener class, to decouple UNO lifetime from EventMultiplexer + + This class gets registered as the XMouse(Motion)Listener on the + XSlideViews, and passes on the events to the EventMultiplexer (via + EventQueue indirection, to force the events into the main thread) + */ +class EventMultiplexerListener : private cppu::BaseMutex, + public Listener_UnoBase +{ +public: + EventMultiplexerListener( EventQueue& rEventQueue, + EventMultiplexerImpl& rEventMultiplexer ) : + Listener_UnoBase( m_aMutex ), + mpEventQueue( &rEventQueue ), + mpEventMultiplexer( &rEventMultiplexer ) + { + } + + EventMultiplexerListener( const EventMultiplexerListener& ) = delete; + EventMultiplexerListener& operator=( const EventMultiplexerListener& ) = delete; + + // WeakComponentImplHelperBase::disposing + virtual void SAL_CALL disposing() override; + +private: + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; + + // XMouseListener implementation + virtual void SAL_CALL mousePressed( const awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseReleased( const awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseEntered( const awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseExited( const awt::MouseEvent& e ) override; + + // XMouseMotionListener implementation + virtual void SAL_CALL mouseDragged( const awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseMoved( const awt::MouseEvent& e ) override; + + + EventQueue* mpEventQueue; + EventMultiplexerImpl* mpEventMultiplexer; +}; + +} + +struct EventMultiplexerImpl +{ + EventMultiplexerImpl( EventQueue& rEventQueue, + UnoViewContainer const& rViewContainer ) : + mrEventQueue(rEventQueue), + mrViewContainer(rViewContainer), + mxListener( new EventMultiplexerListener(rEventQueue, + *this) ), + maNextEffectHandlers(), + maSlideStartHandlers(), + maSlideEndHandlers(), + maAnimationStartHandlers(), + maAnimationEndHandlers(), + maSlideAnimationsEndHandlers(), + maAudioStoppedHandlers(), + maCommandStopAudioHandlers(), + maPauseHandlers(), + maViewHandlers(), + maViewRepaintHandlers(), + maShapeListenerHandlers(), + maUserPaintEventHandlers(), + maMouseClickHandlers(), + maMouseDoubleClickHandlers(), + maMouseMoveHandlers(), + maHyperlinkHandlers(), + mnTimeout(0.0), + mpTickEvent(), + mbIsAutoMode(false) + {} + + ~EventMultiplexerImpl() + { + if( mxListener.is() ) + mxListener->dispose(); + } + + /// Remove all handlers + void clear(); + + // actual handler callbacks (get called from the UNO interface + // listeners via event queue) + void mousePressed( const awt::MouseEvent& e ); + void mouseReleased( const awt::MouseEvent& e ); + void mouseDragged( const awt::MouseEvent& e ); + void mouseMoved( const awt::MouseEvent& e ); + + bool isMouseListenerRegistered() const; + + typedef ThreadUnsafeListenerContainer< + PrioritizedHandlerEntry<EventHandler>, + std::vector< + PrioritizedHandlerEntry<EventHandler> > > ImplNextEffectHandlers; + typedef PrioritizedHandlerEntry<MouseEventHandler> ImplMouseHandlerEntry; + typedef ThreadUnsafeListenerContainer< + ImplMouseHandlerEntry, + std::vector<ImplMouseHandlerEntry> > ImplMouseHandlers; + typedef ThreadUnsafeListenerContainer< + EventHandlerSharedPtr, + std::vector<EventHandlerSharedPtr> > ImplEventHandlers; + typedef ThreadUnsafeListenerContainer< + AnimationEventHandlerSharedPtr, + std::vector<AnimationEventHandlerSharedPtr> > ImplAnimationHandlers; + typedef ThreadUnsafeListenerContainer< + PauseEventHandlerSharedPtr, + std::vector<PauseEventHandlerSharedPtr> > ImplPauseHandlers; + typedef ThreadUnsafeListenerContainer< + ViewEventHandlerWeakPtrWrapper, + std::vector<ViewEventHandlerWeakPtrWrapper> > ImplViewHandlers; + typedef ThreadUnsafeListenerContainer< + ViewRepaintHandlerSharedPtr, + std::vector<ViewRepaintHandlerSharedPtr> > ImplRepaintHandlers; + typedef ThreadUnsafeListenerContainer< + ShapeListenerEventHandlerSharedPtr, + std::vector<ShapeListenerEventHandlerSharedPtr> > ImplShapeListenerHandlers; + typedef ThreadUnsafeListenerContainer< + UserPaintEventHandlerSharedPtr, + std::vector<UserPaintEventHandlerSharedPtr> > ImplUserPaintEventHandlers; + typedef ThreadUnsafeListenerContainer< + PrioritizedHandlerEntry<HyperlinkHandler>, + std::vector<PrioritizedHandlerEntry<HyperlinkHandler> > > ImplHyperLinkHandlers; + + template <typename XSlideShowViewFunc> + void forEachView( XSlideShowViewFunc pViewMethod ); + + UnoViewSharedPtr findUnoView(const uno::Reference< + presentation::XSlideShowView>& xView) const; + + template< typename RegisterFunction > + void addMouseHandler( ImplMouseHandlers& rHandlerContainer, + const MouseEventHandlerSharedPtr& rHandler, + double nPriority, + RegisterFunction pRegisterListener ); + + static bool notifyAllAnimationHandlers( ImplAnimationHandlers const& rContainer, + AnimationNodeSharedPtr const& rNode ); + + bool notifyMouseHandlers( + const ImplMouseHandlers& rQueue, + bool (MouseEventHandler::*pHandlerMethod)( + const awt::MouseEvent& ), + const awt::MouseEvent& e ); + + bool notifyNextEffect(); + + /// Called for automatic nextEffect + void tick(); + + /// Schedules a tick event + void scheduleTick(); + + /// Schedules tick events, if mbIsAutoMode is true + void handleTicks(); + + + EventQueue& mrEventQueue; + UnoViewContainer const& mrViewContainer; + ::rtl::Reference< + EventMultiplexerListener> mxListener; + + ImplNextEffectHandlers maNextEffectHandlers; + ImplEventHandlers maSlideStartHandlers; + ImplEventHandlers maSlideEndHandlers; + ImplAnimationHandlers maAnimationStartHandlers; + ImplAnimationHandlers maAnimationEndHandlers; + ImplEventHandlers maSlideAnimationsEndHandlers; + ImplAnimationHandlers maAudioStoppedHandlers; + ImplAnimationHandlers maCommandStopAudioHandlers; + ImplPauseHandlers maPauseHandlers; + ImplViewHandlers maViewHandlers; + ImplRepaintHandlers maViewRepaintHandlers; + ImplShapeListenerHandlers maShapeListenerHandlers; + ImplUserPaintEventHandlers maUserPaintEventHandlers; + ImplMouseHandlers maMouseClickHandlers; + ImplMouseHandlers maMouseDoubleClickHandlers; + ImplMouseHandlers maMouseMoveHandlers; + ImplHyperLinkHandlers maHyperlinkHandlers; + + /// automatic next effect mode timeout + double mnTimeout; + + /** Holds ptr to optional tick event weakly + + When event queue is cleansed, the next + setAutomaticMode(true) call is then able to + regenerate the event. + */ + ::std::weak_ptr< Event > mpTickEvent; + bool mbIsAutoMode; +}; + + +void SAL_CALL EventMultiplexerListener::disposing() +{ + osl::MutexGuard const guard( m_aMutex ); + mpEventQueue = nullptr; + mpEventMultiplexer = nullptr; +} + +void SAL_CALL EventMultiplexerListener::disposing( + const lang::EventObject& /*rSource*/ ) +{ + // there's no real point in acting on this message - after all, + // the event sources are the XSlideShowViews, which must be + // explicitly removed from the slideshow via + // XSlideShow::removeView(). thus, if a XSlideShowView has + // properly removed itself from the slideshow, it will not be + // found here. and if it hasn't, there'll be other references at + // other places within the slideshow, anyway... +} + +void SAL_CALL EventMultiplexerListener::mousePressed( + const awt::MouseEvent& e ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // notify mouse press. Don't call handlers directly, this + // might not be the main thread! + if( mpEventQueue ) + mpEventQueue->addEvent( + makeEvent( std::bind( &EventMultiplexerImpl::mousePressed, + mpEventMultiplexer, + e ), + "EventMultiplexerImpl::mousePressed") ); +} + +void SAL_CALL EventMultiplexerListener::mouseReleased( + const awt::MouseEvent& e ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // notify mouse release. Don't call handlers directly, + // this might not be the main thread! + if( mpEventQueue ) + mpEventQueue->addEvent( + makeEvent( std::bind( &EventMultiplexerImpl::mouseReleased, + mpEventMultiplexer, + e ), + "EventMultiplexerImpl::mouseReleased") ); +} + +void SAL_CALL EventMultiplexerListener::mouseEntered( + const awt::MouseEvent& /*e*/ ) +{ + // not used here +} + +void SAL_CALL EventMultiplexerListener::mouseExited( + const awt::MouseEvent& /*e*/ ) +{ + // not used here +} + +// XMouseMotionListener implementation +void SAL_CALL EventMultiplexerListener::mouseDragged( + const awt::MouseEvent& e ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // notify mouse drag. Don't call handlers directly, this + // might not be the main thread! + if( mpEventQueue ) + mpEventQueue->addEvent( + makeEvent( std::bind( &EventMultiplexerImpl::mouseDragged, + mpEventMultiplexer, + e ), + "EventMultiplexerImpl::mouseDragged") ); +} + +void SAL_CALL EventMultiplexerListener::mouseMoved( + const awt::MouseEvent& e ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // notify mouse move. Don't call handlers directly, this + // might not be the main thread! + if( mpEventQueue ) + mpEventQueue->addEvent( + makeEvent( std::bind( &EventMultiplexerImpl::mouseMoved, + mpEventMultiplexer, + e ), + "EventMultiplexerImpl::mouseMoved") ); +} + + +bool EventMultiplexerImpl::notifyAllAnimationHandlers( ImplAnimationHandlers const& rContainer, + AnimationNodeSharedPtr const& rNode ) +{ + return rContainer.applyAll( + [&rNode]( const AnimationEventHandlerSharedPtr& pEventHandler ) + { return pEventHandler->handleAnimationEvent( rNode ); } ); +} + +template <typename XSlideShowViewFunc> +void EventMultiplexerImpl::forEachView( XSlideShowViewFunc pViewMethod ) +{ + if( !pViewMethod ) + return; + + // (un)register mouse listener on all views + for( UnoViewVector::const_iterator aIter( mrViewContainer.begin() ), + aEnd( mrViewContainer.end() ); aIter != aEnd; ++aIter ) + { + uno::Reference<presentation::XSlideShowView> xView ((*aIter)->getUnoView()); + if (xView.is()) + { + (xView.get()->*pViewMethod)( mxListener.get() ); + } + else + { + OSL_ASSERT(xView.is()); + } + } +} + +UnoViewSharedPtr EventMultiplexerImpl::findUnoView( + const uno::Reference<presentation::XSlideShowView>& xView) const +{ + // find view from which the change originated + UnoViewVector::const_iterator aIter; + const UnoViewVector::const_iterator aEnd ( mrViewContainer.end() ); + if( (aIter=std::find_if( mrViewContainer.begin(), + aEnd, + [&xView]( const UnoViewSharedPtr& pView ) + { return xView == pView->getUnoView(); } )) == aEnd ) + { + OSL_FAIL("EventMultiplexer::findUnoView(): unexpected message source" ); + return UnoViewSharedPtr(); + } + + return *aIter; +} + +template< typename RegisterFunction > +void EventMultiplexerImpl::addMouseHandler( + ImplMouseHandlers& rHandlerContainer, + const MouseEventHandlerSharedPtr& rHandler, + double nPriority, + RegisterFunction pRegisterListener ) +{ + ENSURE_OR_THROW( + rHandler, + "EventMultiplexer::addMouseHandler(): Invalid handler" ); + + // register mouse listener on all views + forEachView( pRegisterListener ); + + // add into sorted container: + rHandlerContainer.addSorted( + typename ImplMouseHandlers::container_type::value_type( + rHandler, + nPriority )); +} + +bool EventMultiplexerImpl::isMouseListenerRegistered() const +{ + return !(maMouseClickHandlers.isEmpty() && + maMouseDoubleClickHandlers.isEmpty()); +} + +void EventMultiplexerImpl::tick() +{ + if( !mbIsAutoMode ) + return; // this event is just a left-over, ignore + + notifyNextEffect(); + + if( !maNextEffectHandlers.isEmpty() ) + { + // still handlers left, schedule next timeout + // event. Will also set mbIsTickEventOn back to true + scheduleTick(); + } +} + +void EventMultiplexerImpl::scheduleTick() +{ + EventSharedPtr pEvent( + makeDelay( [this] () { this->tick(); }, + mnTimeout, + "EventMultiplexerImpl::tick with delay")); + + // store weak reference to generated event, to notice when + // the event queue gets cleansed (we then have to + // regenerate the tick event!) + mpTickEvent = pEvent; + + // enabled auto mode: simply schedule a timeout event, + // which will eventually call our tick() method + mrEventQueue.addEventForNextRound( pEvent ); +} + +void EventMultiplexerImpl::handleTicks() +{ + if( !mbIsAutoMode ) + return; // nothing to do, don't need no ticks + + EventSharedPtr pTickEvent( mpTickEvent.lock() ); + if( pTickEvent ) + return; // nothing to do, there's already a tick + // pending + + // schedule initial tick (which reschedules itself + // after that, all by itself) + scheduleTick(); +} + + +void EventMultiplexerImpl::clear() +{ + // deregister from all views. + if( isMouseListenerRegistered() ) + { + for( UnoViewVector::const_iterator aIter=mrViewContainer.begin(), + aEnd=mrViewContainer.end(); + aIter!=aEnd; + ++aIter ) + { + if( (*aIter)->getUnoView().is() ) + (*aIter)->getUnoView()->removeMouseListener( mxListener ); + } + } + + if( !maMouseMoveHandlers.isEmpty() ) + { + for( UnoViewVector::const_iterator aIter=mrViewContainer.begin(), + aEnd=mrViewContainer.end(); + aIter!=aEnd; + ++aIter ) + { + if( (*aIter)->getUnoView().is() ) + (*aIter)->getUnoView()->removeMouseMotionListener( mxListener ); + } + } + + // clear all handlers (releases all references) + maNextEffectHandlers.clear(); + maSlideStartHandlers.clear(); + maSlideEndHandlers.clear(); + maAnimationStartHandlers.clear(); + maAnimationEndHandlers.clear(); + maSlideAnimationsEndHandlers.clear(); + maAudioStoppedHandlers.clear(); + maCommandStopAudioHandlers.clear(); + maPauseHandlers.clear(); + maViewHandlers.clear(); + maViewRepaintHandlers.clear(); + maMouseClickHandlers.clear(); + maMouseDoubleClickHandlers.clear(); + maMouseMoveHandlers.clear(); + maHyperlinkHandlers.clear(); + mpTickEvent.reset(); +} + +// XMouseListener implementation +bool EventMultiplexerImpl::notifyMouseHandlers( + const ImplMouseHandlers& rQueue, + bool (MouseEventHandler::*pHandlerMethod)( const awt::MouseEvent& ), + const awt::MouseEvent& e ) +{ + uno::Reference<presentation::XSlideShowView> xView( + e.Source, uno::UNO_QUERY ); + + ENSURE_OR_RETURN_FALSE( xView.is(), "EventMultiplexer::notifyHandlers(): " + "event source is not an XSlideShowView" ); + + // find corresponding view (to map mouse position into user + // coordinate space) + UnoViewVector::const_iterator aIter; + const UnoViewVector::const_iterator aEnd ( mrViewContainer.end() ); + if( (aIter=::std::find_if( + mrViewContainer.begin(), + aEnd, + [&xView]( const UnoViewSharedPtr& pView ) + { return xView == pView->getUnoView(); } )) == aEnd ) + { + ENSURE_OR_RETURN_FALSE( + false, "EventMultiplexer::notifyHandlers(): " + "event source not found under registered views" ); + } + + // convert mouse position to user coordinate space + ::basegfx::B2DPoint aPosition( e.X, e.Y ); + ::basegfx::B2DHomMatrix aMatrix( (*aIter)->getTransformation() ); + if( !aMatrix.invert() ) + ENSURE_OR_THROW( false, "EventMultiplexer::notifyHandlers():" + " view matrix singular" ); + aPosition *= aMatrix; + + awt::MouseEvent aEvent( e ); + aEvent.X = ::basegfx::fround( aPosition.getX() ); + aEvent.Y = ::basegfx::fround( aPosition.getY() ); + + // fire event on handlers, try in order of precedence. If + // one high-priority handler rejects the event + // (i.e. returns false), try next handler. + return rQueue.apply( + [&pHandlerMethod, &aEvent]( const ImplMouseHandlerEntry& rMouseHandler ) + { return ( ( *rMouseHandler.getHandler() ).*pHandlerMethod )( aEvent ); } ); +} + +void EventMultiplexerImpl::mousePressed( const awt::MouseEvent& e ) +{ + // fire double-click events for every second click + sal_Int32 nCurrClickCount = e.ClickCount; + while( nCurrClickCount > 1 && + notifyMouseHandlers( maMouseDoubleClickHandlers, + &MouseEventHandler::handleMousePressed, + e )) + { + nCurrClickCount -= 2; + } + + // fire single-click events for all remaining clicks + while( nCurrClickCount > 0 && + notifyMouseHandlers( maMouseClickHandlers, + &MouseEventHandler::handleMousePressed, + e )) + { + --nCurrClickCount; + } +} + +void EventMultiplexerImpl::mouseReleased( const awt::MouseEvent& e ) +{ + // fire double-click events for every second click + sal_Int32 nCurrClickCount = e.ClickCount; + while( nCurrClickCount > 1 && + notifyMouseHandlers( maMouseDoubleClickHandlers, + &MouseEventHandler::handleMouseReleased, + e )) + { + nCurrClickCount -= 2; + } + + // fire single-click events for all remaining clicks + while( nCurrClickCount > 0 && + notifyMouseHandlers( maMouseClickHandlers, + &MouseEventHandler::handleMouseReleased, + e )) + { + --nCurrClickCount; + } +} + +void EventMultiplexerImpl::mouseDragged( const awt::MouseEvent& e ) +{ + notifyMouseHandlers( maMouseMoveHandlers, + &MouseEventHandler::handleMouseDragged, + e ); +} + +void EventMultiplexerImpl::mouseMoved( const awt::MouseEvent& e ) +{ + notifyMouseHandlers( maMouseMoveHandlers, + &MouseEventHandler::handleMouseMoved, + e ); +} + +bool EventMultiplexerImpl::notifyNextEffect() +{ + // fire event on handlers, try in order of precedence. If one + // high-priority handler rejects the event (i.e. returns false), + // try next handler. + return maNextEffectHandlers.apply( + []( const PrioritizedHandlerEntry< EventHandler >& pHandler ) + { return pHandler.getHandler()->handleEvent(); } ); +} + + +EventMultiplexer::EventMultiplexer( EventQueue& rEventQueue, + UnoViewContainer const& rViewContainer ) : + mpImpl( new EventMultiplexerImpl(rEventQueue, rViewContainer) ) +{ +} + +EventMultiplexer::~EventMultiplexer() +{ + // outline because of EventMultiplexerImpl's incomplete type +} + +void EventMultiplexer::clear() +{ + mpImpl->clear(); +} + +void EventMultiplexer::setAutomaticMode( bool bIsAuto ) +{ + if( bIsAuto == mpImpl->mbIsAutoMode ) + return; // no change, nothing to do + + mpImpl->mbIsAutoMode = bIsAuto; + + mpImpl->handleTicks(); +} + +bool EventMultiplexer::getAutomaticMode() const +{ + return mpImpl->mbIsAutoMode; +} + +void EventMultiplexer::setAutomaticTimeout( double nTimeout ) +{ + mpImpl->mnTimeout = nTimeout; +} + +double EventMultiplexer::getAutomaticTimeout() const +{ + return mpImpl->mnTimeout; +} + +void EventMultiplexer::addNextEffectHandler( + EventHandlerSharedPtr const& rHandler, + double nPriority ) +{ + mpImpl->maNextEffectHandlers.addSorted( + EventMultiplexerImpl::ImplNextEffectHandlers::container_type::value_type( + rHandler, + nPriority) ); + + // Enable tick events, if not done already + mpImpl->handleTicks(); +} + +void EventMultiplexer::removeNextEffectHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maNextEffectHandlers.remove( + EventMultiplexerImpl::ImplNextEffectHandlers::container_type::value_type( + rHandler, + 0.0) ); +} + +void EventMultiplexer::addSlideStartHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideStartHandlers.add( rHandler ); +} + +void EventMultiplexer::removeSlideStartHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideStartHandlers.remove( rHandler ); +} + +void EventMultiplexer::addSlideEndHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideEndHandlers.add( rHandler ); +} + +void EventMultiplexer::removeSlideEndHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideEndHandlers.remove( rHandler ); +} + +void EventMultiplexer::addAnimationStartHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAnimationStartHandlers.add( rHandler ); +} + +void EventMultiplexer::removeAnimationStartHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAnimationStartHandlers.remove( rHandler ); +} + +void EventMultiplexer::addAnimationEndHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAnimationEndHandlers.add( rHandler ); +} + +void EventMultiplexer::removeAnimationEndHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAnimationEndHandlers.remove( rHandler ); +} + +void EventMultiplexer::addSlideAnimationsEndHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideAnimationsEndHandlers.add( rHandler ); +} + +void EventMultiplexer::removeSlideAnimationsEndHandler( + const EventHandlerSharedPtr& rHandler ) +{ + mpImpl->maSlideAnimationsEndHandlers.remove( rHandler ); +} + +void EventMultiplexer::addAudioStoppedHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAudioStoppedHandlers.add( rHandler ); +} + +void EventMultiplexer::removeAudioStoppedHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maAudioStoppedHandlers.remove( rHandler ); +} + +void EventMultiplexer::addCommandStopAudioHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maCommandStopAudioHandlers.add( rHandler ); +} + +void EventMultiplexer::removeCommandStopAudioHandler( + const AnimationEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maCommandStopAudioHandlers.remove( rHandler ); +} + +void EventMultiplexer::addPauseHandler( + const PauseEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maPauseHandlers.add( rHandler ); +} + +void EventMultiplexer::removePauseHandler( + const PauseEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maPauseHandlers.remove( rHandler ); +} + +void EventMultiplexer::addViewHandler( + const ViewEventHandlerWeakPtr& rHandler ) +{ + mpImpl->maViewHandlers.add( rHandler ); +} + +void EventMultiplexer::removeViewHandler( const ViewEventHandlerWeakPtr& rHandler ) +{ + mpImpl->maViewHandlers.remove( rHandler ); +} + +void EventMultiplexer::addViewRepaintHandler( const ViewRepaintHandlerSharedPtr& rHandler ) +{ + mpImpl->maViewRepaintHandlers.add( rHandler ); +} + +void EventMultiplexer::removeViewRepaintHandler( const ViewRepaintHandlerSharedPtr& rHandler ) +{ + mpImpl->maViewRepaintHandlers.remove( rHandler ); +} + +void EventMultiplexer::addShapeListenerHandler( const ShapeListenerEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maShapeListenerHandlers.add( rHandler ); +} + +void EventMultiplexer::removeShapeListenerHandler( const ShapeListenerEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maShapeListenerHandlers.remove( rHandler ); +} + +void EventMultiplexer::addUserPaintHandler( const UserPaintEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maUserPaintEventHandlers.add( rHandler ); +} + +void EventMultiplexer::addClickHandler( + const MouseEventHandlerSharedPtr& rHandler, + double nPriority ) +{ + mpImpl->addMouseHandler( + mpImpl->maMouseClickHandlers, + rHandler, + nPriority, + mpImpl->isMouseListenerRegistered() + ? nullptr + : &presentation::XSlideShowView::addMouseListener ); +} + +void EventMultiplexer::removeClickHandler( + const MouseEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maMouseClickHandlers.remove( + EventMultiplexerImpl::ImplMouseHandlers::container_type::value_type( + rHandler, + 0.0) ); + + if( !mpImpl->isMouseListenerRegistered() ) + mpImpl->forEachView( &presentation::XSlideShowView::removeMouseListener ); +} + +void EventMultiplexer::addDoubleClickHandler( + const MouseEventHandlerSharedPtr& rHandler, + double nPriority ) +{ + mpImpl->addMouseHandler( + mpImpl->maMouseDoubleClickHandlers, + rHandler, + nPriority, + mpImpl->isMouseListenerRegistered() + ? nullptr + : &presentation::XSlideShowView::addMouseListener ); +} + +void EventMultiplexer::removeDoubleClickHandler( + const MouseEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maMouseDoubleClickHandlers.remove( + EventMultiplexerImpl::ImplMouseHandlers::container_type::value_type( + rHandler, + 0.0) ); + + if( !mpImpl->isMouseListenerRegistered() ) + mpImpl->forEachView( &presentation::XSlideShowView::removeMouseListener ); +} + +void EventMultiplexer::addMouseMoveHandler( + const MouseEventHandlerSharedPtr& rHandler, + double nPriority ) +{ + mpImpl->addMouseHandler( + mpImpl->maMouseMoveHandlers, + rHandler, + nPriority, + mpImpl->maMouseMoveHandlers.isEmpty() + ? &presentation::XSlideShowView::addMouseMotionListener + : nullptr ); +} + +void EventMultiplexer::removeMouseMoveHandler( + const MouseEventHandlerSharedPtr& rHandler ) +{ + mpImpl->maMouseMoveHandlers.remove( + EventMultiplexerImpl::ImplMouseHandlers::container_type::value_type( + rHandler, + 0.0) ); + + if( mpImpl->maMouseMoveHandlers.isEmpty() ) + mpImpl->forEachView( + &presentation::XSlideShowView::removeMouseMotionListener ); +} + +void EventMultiplexer::addHyperlinkHandler( const HyperlinkHandlerSharedPtr& rHandler, + double nPriority ) +{ + mpImpl->maHyperlinkHandlers.addSorted( + EventMultiplexerImpl::ImplHyperLinkHandlers::container_type::value_type( + rHandler, + nPriority) ); +} + +void EventMultiplexer::removeHyperlinkHandler( const HyperlinkHandlerSharedPtr& rHandler ) +{ + mpImpl->maHyperlinkHandlers.remove( + EventMultiplexerImpl::ImplHyperLinkHandlers::container_type::value_type( + rHandler, + 0.0) ); +} + +void EventMultiplexer::notifyShapeListenerAdded( + const uno::Reference<drawing::XShape>& xShape ) +{ + mpImpl->maShapeListenerHandlers.applyAll( + [&xShape]( const ShapeListenerEventHandlerSharedPtr& pHandler ) + { return pHandler->listenerAdded( xShape ); } ); +} + +void EventMultiplexer::notifyShapeListenerRemoved( + const uno::Reference<drawing::XShape>& xShape ) +{ + mpImpl->maShapeListenerHandlers.applyAll( + [&xShape]( const ShapeListenerEventHandlerSharedPtr& pHandler ) + { return pHandler->listenerRemoved( xShape ); } ); +} + +void EventMultiplexer::notifyUserPaintColor( RGBColor const& rUserColor ) +{ + mpImpl->maUserPaintEventHandlers.applyAll( + [&rUserColor]( const UserPaintEventHandlerSharedPtr& pHandler ) + { return pHandler->colorChanged( rUserColor ); } ); +} + +void EventMultiplexer::notifyUserPaintStrokeWidth( double rUserStrokeWidth ) +{ + mpImpl->maUserPaintEventHandlers.applyAll( + [&rUserStrokeWidth]( const UserPaintEventHandlerSharedPtr& pHandler ) + { return pHandler->widthChanged( rUserStrokeWidth ); } ); +} + +void EventMultiplexer::notifyUserPaintDisabled() +{ + mpImpl->maUserPaintEventHandlers.applyAll( + std::mem_fn(&UserPaintEventHandler::disable)); +} + +void EventMultiplexer::notifySwitchPenMode(){ + mpImpl->maUserPaintEventHandlers.applyAll( + std::mem_fn(&UserPaintEventHandler::switchPenMode)); +} + +void EventMultiplexer::notifySwitchEraserMode(){ + mpImpl->maUserPaintEventHandlers.applyAll( + std::mem_fn(&UserPaintEventHandler::switchEraserMode)); +} + +//adding erasing all ink features with UserPaintOverlay +void EventMultiplexer::notifyEraseAllInk( bool bEraseAllInk ) +{ + mpImpl->maUserPaintEventHandlers.applyAll( + [&bEraseAllInk]( const UserPaintEventHandlerSharedPtr& pHandler ) + { return pHandler->eraseAllInkChanged( bEraseAllInk ); } ); +} + +//adding erasing features with UserPaintOverlay +void EventMultiplexer::notifyEraseInkWidth( sal_Int32 rEraseInkSize ) +{ + mpImpl->maUserPaintEventHandlers.applyAll( + [&rEraseInkSize]( const UserPaintEventHandlerSharedPtr& pHandler ) + { return pHandler->eraseInkWidthChanged( rEraseInkSize ); } ); +} + +bool EventMultiplexer::notifyNextEffect() +{ + return mpImpl->notifyNextEffect(); +} + +void EventMultiplexer::notifySlideStartEvent() +{ + mpImpl->maSlideStartHandlers.applyAll( + std::mem_fn(&EventHandler::handleEvent) ); +} + +bool EventMultiplexer::notifySlideEndEvent() +{ + return mpImpl->maSlideEndHandlers.applyAll( + std::mem_fn(&EventHandler::handleEvent) ); +} + +bool EventMultiplexer::notifyAnimationStart( + const AnimationNodeSharedPtr& rNode ) +{ + return EventMultiplexerImpl::notifyAllAnimationHandlers( mpImpl->maAnimationStartHandlers, + rNode ); +} + +bool EventMultiplexer::notifyAnimationEnd( + const AnimationNodeSharedPtr& rNode ) +{ + return EventMultiplexerImpl::notifyAllAnimationHandlers( mpImpl->maAnimationEndHandlers, + rNode ); +} + +bool EventMultiplexer::notifySlideAnimationsEnd() +{ + return mpImpl->maSlideAnimationsEndHandlers.applyAll( + std::mem_fn(&EventHandler::handleEvent)); +} + +bool EventMultiplexer::notifyAudioStopped( + const AnimationNodeSharedPtr& rNode ) +{ + return EventMultiplexerImpl::notifyAllAnimationHandlers( + mpImpl->maAudioStoppedHandlers, + rNode ); +} + +bool EventMultiplexer::notifyCommandStopAudio( + const AnimationNodeSharedPtr& rNode ) +{ + return EventMultiplexerImpl::notifyAllAnimationHandlers( + mpImpl->maCommandStopAudioHandlers, + rNode ); +} + +void EventMultiplexer::notifyPauseMode( bool bPauseShow ) +{ + mpImpl->maPauseHandlers.applyAll( + [&bPauseShow]( const PauseEventHandlerSharedPtr& pHandler ) + { return pHandler->handlePause( bPauseShow ); } ); +} + +void EventMultiplexer::notifyViewAdded( const UnoViewSharedPtr& rView ) +{ + ENSURE_OR_THROW( rView, "EventMultiplexer::notifyViewAdded(): Invalid view"); + + // register event listener + uno::Reference<presentation::XSlideShowView> const rUnoView( + rView->getUnoView() ); + + if( mpImpl->isMouseListenerRegistered() ) + rUnoView->addMouseListener( + mpImpl->mxListener ); + + if( !mpImpl->maMouseMoveHandlers.isEmpty() ) + rUnoView->addMouseMotionListener( + mpImpl->mxListener ); + + mpImpl->maViewHandlers.applyAll( + [&rView]( const ViewEventHandlerWeakPtr& pHandler ) + { return pHandler.lock()->viewAdded( rView ); } ); +} + +void EventMultiplexer::notifyViewRemoved( const UnoViewSharedPtr& rView ) +{ + ENSURE_OR_THROW( rView, + "EventMultiplexer::removeView(): Invalid view" ); + + // revoke event listeners + uno::Reference<presentation::XSlideShowView> const rUnoView( + rView->getUnoView() ); + + if( mpImpl->isMouseListenerRegistered() ) + rUnoView->removeMouseListener( + mpImpl->mxListener ); + + if( !mpImpl->maMouseMoveHandlers.isEmpty() ) + rUnoView->removeMouseMotionListener( + mpImpl->mxListener ); + + mpImpl->maViewHandlers.applyAll( + [&rView]( const ViewEventHandlerWeakPtr& pHandler ) + { return pHandler.lock()->viewRemoved( rView ); } ); +} + +void EventMultiplexer::notifyViewChanged( const UnoViewSharedPtr& rView ) +{ + mpImpl->maViewHandlers.applyAll( + [&rView]( const ViewEventHandlerWeakPtr& pHandler ) + { return pHandler.lock()->viewChanged( rView ); } ); +} + +void EventMultiplexer::notifyViewChanged( const uno::Reference<presentation::XSlideShowView>& xView ) +{ + UnoViewSharedPtr pView( mpImpl->findUnoView(xView) ); + + if( !pView ) + return; // view not registered here + + notifyViewChanged( pView ); +} + +void EventMultiplexer::notifyViewsChanged() +{ + mpImpl->maViewHandlers.applyAll( + std::mem_fn( &ViewEventHandler::viewsChanged )); +} + +void EventMultiplexer::notifyViewClobbered( + const uno::Reference<presentation::XSlideShowView>& xView ) +{ + UnoViewSharedPtr pView( mpImpl->findUnoView(xView) ); + + if( !pView ) + return; // view not registered here + + mpImpl->maViewRepaintHandlers.applyAll( + [&pView]( const ViewRepaintHandlerSharedPtr& pHandler ) + { return pHandler->viewClobbered( pView ); } ); +} + +void EventMultiplexer::notifyHyperlinkClicked( + OUString const& hyperLink ) +{ + mpImpl->maHyperlinkHandlers.apply( + [&hyperLink]( const PrioritizedHandlerEntry< HyperlinkHandler >& pHandler ) + { return pHandler.getHandler()->handleHyperlink( hyperLink ); } ); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/eventqueue.cxx b/slideshow/source/engine/eventqueue.cxx new file mode 100644 index 000000000..b63db471d --- /dev/null +++ b/slideshow/source/engine/eventqueue.cxx @@ -0,0 +1,295 @@ +/* -*- 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 <sal/log.hxx> + +#include <event.hxx> +#include <eventqueue.hxx> +#include <slideshowexceptions.hxx> + +#include <limits> +#include <memory> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + bool EventQueue::EventEntry::operator<( const EventEntry& rEvent ) const + { + // negate comparison, we want priority queue to be sorted + // in increasing order of activation times + return nTime > rEvent.nTime; + } + + + EventQueue::EventQueue( + std::shared_ptr<canvas::tools::ElapsedTime> const & pPresTimer ) + : maMutex(), + maEvents(), + maNextEvents(), + maNextNextEvents(), + mpTimer( pPresTimer ) + { + } + + EventQueue::~EventQueue() + { + // add in all that have been added explicitly for this round: + for ( const auto& rEvent : maNextEvents ) + { + maEvents.push(rEvent); + } + EventEntryVector().swap( maNextEvents ); + + // dispose event queue + while( !maEvents.empty() ) + { + try + { + maEvents.top().pEvent->dispose(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + maEvents.pop(); + } + } + + bool EventQueue::addEvent( const EventSharedPtr& rEvent ) + { + std::unique_lock aGuard( maMutex ); + + SAL_INFO("slideshow.eventqueue", "adding event \"" << rEvent->GetDescription() + << "\" [" << rEvent.get() + << "] at " << mpTimer->getElapsedTime() + << " with delay " << rEvent->getActivationTime(0.0) + ); + ENSURE_OR_RETURN_FALSE( rEvent, + "EventQueue::addEvent: event ptr NULL" ); + + // prepare entry + + // A seemingly obvious optimization cannot be used here, + // because it breaks assumed order of notification: zero + // timeout events could be fired() immediately, but that + // would not unwind the stack and furthermore changes + // order of notification + + // add entry + maEvents.push( EventEntry( rEvent, rEvent->getActivationTime( + mpTimer->getElapsedTime()) ) ); + return true; + } + + bool EventQueue::addEventForNextRound( EventSharedPtr const& rEvent ) + { + std::unique_lock aGuard( maMutex ); + + SAL_INFO("slideshow.eventqueue", "adding event \"" << rEvent->GetDescription() + << "\" [" << rEvent.get() + << "] for the next round at " << mpTimer->getElapsedTime() + << " with delay " << rEvent->getActivationTime(0.0) + ); + + ENSURE_OR_RETURN_FALSE( rEvent, + "EventQueue::addEvent: event ptr NULL" ); + maNextEvents.emplace_back( rEvent, rEvent->getActivationTime( + mpTimer->getElapsedTime()) ); + return true; + } + + bool EventQueue::addEventWhenQueueIsEmpty (const EventSharedPtr& rpEvent) + { + std::unique_lock aGuard( maMutex ); + + SAL_INFO("slideshow.eventqueue", "adding event \"" << rpEvent->GetDescription() + << "\" [" << rpEvent.get() + << "] for execution when the queue is empty at " << mpTimer->getElapsedTime() + << " with delay " << rpEvent->getActivationTime(0.0) + ); + + ENSURE_OR_RETURN_FALSE( rpEvent, "EventQueue::addEvent: event ptr NULL"); + + maNextNextEvents.push( + EventEntry( + rpEvent, + rpEvent->getActivationTime(mpTimer->getElapsedTime()))); + + return true; + } + + void EventQueue::forceEmpty() + { + process_(true); + } + + void EventQueue::process() + { + process_(false); + } + + void EventQueue::process_( bool bFireAllEvents ) + { + std::unique_lock aGuard( maMutex ); + + SAL_INFO("slideshow.verbose", "EventQueue: heartbeat" ); + + // add in all that have been added explicitly for this round: + for ( const auto& rEvent : maNextEvents ) { + maEvents.push(rEvent); + } + EventEntryVector().swap( maNextEvents ); + + // perform topmost, ready-to-execute event + // ======================================= + + const double nCurrTime( mpTimer->getElapsedTime() ); + + // When maEvents does not contain any events that are due now + // then process one event from maNextNextEvents. + if (!maNextNextEvents.empty() + && !bFireAllEvents + && (maEvents.empty() || maEvents.top().nTime > nCurrTime)) + { + const EventEntry aEvent (std::move(maNextNextEvents.top())); + maNextNextEvents.pop(); + maEvents.push(aEvent); + } + + // process ready/elapsed events. Note that the 'perceived' + // current time remains constant for this loop, thus we're + // processing only those events which where ready when we + // entered this method. + while( !maEvents.empty() && + (bFireAllEvents || maEvents.top().nTime <= nCurrTime) ) + { + EventEntry event( std::move(maEvents.top()) ); + maEvents.pop(); + + // only process event, if it is still 'charged', + // i.e. the fire() call effects something. This is + // used when e.g. having events registered at multiple + // places, which should fire only once: after the + // initial fire() call, those events become inactive + // and return false on isCharged. This frees us from + // the need to prune queues of those inactive shells. + if( event.pEvent->isCharged() ) + { + aGuard.unlock(); + try + { + SAL_INFO("slideshow.eventqueue", "firing event \"" + << event.pEvent->GetDescription() + << "\" [" << event.pEvent.get() + << "] at " << mpTimer->getElapsedTime() + << " with delay " << event.pEvent->getActivationTime(0.0) + ); + event.pEvent->fire(); + SAL_INFO("slideshow.eventqueue", "event \"" + << event.pEvent->GetDescription() + << "\" [" << event.pEvent.get() << "] fired" + ); + } + 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.eventqueue", "::presentation::internal::EventQueue: Event threw a SlideShowException, action might not have been fully performed" ); + } + aGuard.lock(); + } + else + { + SAL_INFO( + "slideshow.eventqueue", + "Ignoring discharged event: unknown (" + << event.pEvent.get() << "), timeout was: " + << event.pEvent->getActivationTime(0.0)); + } + } + } + + bool EventQueue::isEmpty() const + { + std::unique_lock aGuard( maMutex ); + + return maEvents.empty() && maNextEvents.empty() && maNextNextEvents.empty(); + } + + double EventQueue::nextTimeout() const + { + std::unique_lock aGuard( maMutex ); + + // return time for next entry (if any) + double nTimeout (::std::numeric_limits<double>::max()); + const double nCurrentTime (mpTimer->getElapsedTime()); + if ( ! maEvents.empty()) + nTimeout = maEvents.top().nTime - nCurrentTime; + if ( ! maNextEvents.empty()) + nTimeout = ::std::min(nTimeout, maNextEvents.front().nTime - nCurrentTime); + if ( ! maNextNextEvents.empty()) + nTimeout = ::std::min(nTimeout, maNextNextEvents.top().nTime - nCurrentTime); + + return nTimeout; + } + + void EventQueue::clear() + { + std::unique_lock aGuard( maMutex ); + + // TODO(P1): Maybe a plain vector and vector.swap will + // be faster here. Profile. + maEvents = ImplQueueType(); + + maNextEvents.clear(); + maNextNextEvents = ImplQueueType(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/expressionnodefactory.cxx b/slideshow/source/engine/expressionnodefactory.cxx new file mode 100644 index 000000000..0b4136f34 --- /dev/null +++ b/slideshow/source/engine/expressionnodefactory.cxx @@ -0,0 +1,239 @@ +/* -*- 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 <expressionnodefactory.hxx> + +#include <algorithm> + + +/* Implementation of ExpressionNodeFactory class */ + +namespace slideshow::internal +{ + namespace + { + class ConstantValueExpression : public ExpressionNode + { + public: + explicit ConstantValueExpression( double rValue ) : + maValue( rValue ) + { + } + + virtual double operator()( double /*t*/ ) const override + { + return maValue; + } + + virtual bool isConstant() const override + { + return true; + } + + private: + double maValue; + }; + + class TValueExpression : public ExpressionNode + { + public: + TValueExpression() + { + } + + virtual double operator()( double t ) const override + { + return t; + } + + virtual bool isConstant() const override + { + return false; + } + }; + + /** Base class for following binary functions (*+-/) + + Does not pay off to have all this as a template, since + we'd have to hold the functor as a member (+33% object + size). + */ + class BinaryExpressionBase : public ExpressionNode + { + public: + BinaryExpressionBase( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + mpFirstArg( rFirstArg ), + mpSecondArg( rSecondArg ) + { + } + + virtual bool isConstant() const override + { + return + mpFirstArg->isConstant() && + mpSecondArg->isConstant(); + } + + protected: + std::shared_ptr<ExpressionNode> mpFirstArg; + std::shared_ptr<ExpressionNode> mpSecondArg; + }; + + class PlusExpression : public BinaryExpressionBase + { + public: + PlusExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return (*mpFirstArg)(t) + (*mpSecondArg)(t); + } + }; + + class MinusExpression : public BinaryExpressionBase + { + public: + MinusExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return (*mpFirstArg)(t) - (*mpSecondArg)(t); + } + }; + + class MultipliesExpression : public BinaryExpressionBase + { + public: + MultipliesExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return (*mpFirstArg)(t) * (*mpSecondArg)(t); + } + }; + + class DividesExpression : public BinaryExpressionBase + { + public: + DividesExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return (*mpFirstArg)(t) / (*mpSecondArg)(t); + } + }; + + class MinExpression : public BinaryExpressionBase + { + public: + MinExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return ::std::min( (*mpFirstArg)(t), (*mpSecondArg)(t) ); + } + }; + + class MaxExpression : public BinaryExpressionBase + { + public: + MaxExpression( const std::shared_ptr<ExpressionNode>& rFirstArg, + const std::shared_ptr<ExpressionNode>& rSecondArg ) : + BinaryExpressionBase( rFirstArg, rSecondArg ) + { + } + + virtual double operator()( double t ) const override + { + return ::std::max( (*mpFirstArg)(t), (*mpSecondArg)(t) ); + } + }; + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createConstantValueExpression( double rConstantValue ) + { + return std::make_shared<ConstantValueExpression>(rConstantValue); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createValueTExpression() + { + return std::make_shared<TValueExpression>(); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createPlusExpression( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ) + { + return std::make_shared<PlusExpression>(rLHS, rRHS); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createMinusExpression( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ) + { + return std::make_shared<MinusExpression>(rLHS, rRHS); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createMultipliesExpression( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ) + { + return std::make_shared<MultipliesExpression>(rLHS, rRHS); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createDividesExpression( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ) + { + return std::make_shared<DividesExpression>(rLHS, rRHS); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createMinExpression ( const std::shared_ptr<ExpressionNode>& rOuterFunction, + const std::shared_ptr<ExpressionNode>& rInnerFunction ) + { + return std::make_shared<MinExpression>(rOuterFunction, rInnerFunction); + } + + std::shared_ptr<ExpressionNode> ExpressionNodeFactory::createMaxExpression ( const std::shared_ptr<ExpressionNode>& rOuterFunction, + const std::shared_ptr<ExpressionNode>& rInnerFunction ) + { + return std::make_shared<MaxExpression>(rOuterFunction, rInnerFunction); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/Operation.cxx b/slideshow/source/engine/opengl/Operation.cxx new file mode 100644 index 000000000..ec83d6b74 --- /dev/null +++ b/slideshow/source/engine/opengl/Operation.cxx @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2015 by Collabora, Ltd. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/config.h> + +#include "Operation.hxx" + +#include <basegfx/numeric/ftools.hxx> + +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> + +SRotate::SRotate(const glm::vec3& Axis, const glm::vec3& Origin, + double Angle, bool bInter, double T0, double T1): + Operation(bInter, T0, T1), + axis(Axis), + origin(Origin), + angle(basegfx::deg2rad(Angle)) +{ +} + +SScale::SScale(const glm::vec3& Scale, const glm::vec3& Origin, + bool bInter, double T0, double T1): + Operation(bInter, T0, T1), + scale(Scale), + origin(Origin) +{ +} + +RotateAndScaleDepthByWidth::RotateAndScaleDepthByWidth(const glm::vec3& Axis, + const glm::vec3& Origin, double Angle, bool bScale, bool bInter, double T0, double T1): + Operation(bInter, T0, T1), + axis(Axis), + origin(Origin), + angle(basegfx::deg2rad(Angle)), + scale(bScale) +{ +} + +RotateAndScaleDepthByHeight::RotateAndScaleDepthByHeight(const glm::vec3& Axis, + const glm::vec3& Origin, double Angle, bool bScale, bool bInter, double T0, double T1): + Operation(bInter, T0, T1), + axis(Axis), + origin(Origin), + angle(basegfx::deg2rad(Angle)), + scale(bScale) +{ +} + + +STranslate::STranslate(const glm::vec3& Vector, bool bInter, double T0, double T1): + Operation(bInter, T0, T1), + vector(Vector) +{ +} + +std::shared_ptr<SRotate> +makeSRotate(const glm::vec3& Axis,const glm::vec3& Origin,double Angle,bool bInter, double T0, double T1) +{ + return std::make_shared<SRotate>(Axis, Origin, Angle, bInter, T0, T1); +} + +std::shared_ptr<SScale> +makeSScale(const glm::vec3& Scale, const glm::vec3& Origin,bool bInter, double T0, double T1) +{ + return std::make_shared<SScale>(Scale, Origin, bInter, T0, T1); +} + +std::shared_ptr<STranslate> +makeSTranslate(const glm::vec3& Vector,bool bInter, double T0, double T1) +{ + return std::make_shared<STranslate>(Vector, bInter, T0, T1); +} + +std::shared_ptr<SEllipseTranslate> +makeSEllipseTranslate(double dWidth, double dHeight, double dStartPosition, double dEndPosition, bool bInter, double T0, double T1) +{ + return std::make_shared<SEllipseTranslate>(dWidth, dHeight, dStartPosition, dEndPosition, bInter, T0, T1); +} + +std::shared_ptr<RotateAndScaleDepthByWidth> +makeRotateAndScaleDepthByWidth(const glm::vec3& Axis,const glm::vec3& Origin,double Angle, bool bScale, bool bInter, double T0, double T1) +{ + return std::make_shared<RotateAndScaleDepthByWidth>(Axis, Origin, Angle, bScale, bInter, T0, T1); +} + +std::shared_ptr<RotateAndScaleDepthByHeight> +makeRotateAndScaleDepthByHeight(const glm::vec3& Axis,const glm::vec3& Origin,double Angle,bool bScale, bool bInter, double T0, double T1) +{ + return std::make_shared<RotateAndScaleDepthByHeight>(Axis, Origin, Angle, bScale, bInter, T0, T1); +} + +static double intervalInter(double t, double T0, double T1) +{ + return ( t - T0 ) / ( T1 - T0 ); +} + +void STranslate::interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + matrix = glm::translate(matrix, glm::vec3(SlideWidthScale*t*vector.x, SlideHeightScale*t*vector.y, t*vector.z)); +} + +void SRotate::interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + glm::vec3 translation_vector(SlideWidthScale*origin.x, SlideHeightScale*origin.y, origin.z); + glm::vec3 scale_vector(SlideWidthScale * SlideWidthScale, SlideHeightScale * SlideHeightScale, 1); + matrix = glm::translate(matrix, translation_vector); + matrix = glm::scale(matrix, scale_vector); + matrix = glm::rotate(matrix, static_cast<float>(t*angle), axis); + matrix = glm::scale(matrix, 1.f / scale_vector); + matrix = glm::translate(matrix, -translation_vector); +} + +void SScale::interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + glm::vec3 translation_vector(SlideWidthScale*origin.x, SlideHeightScale*origin.y, origin.z); + matrix = glm::translate(matrix, translation_vector); + matrix = glm::scale(matrix, static_cast<float>(1 - t) + static_cast<float>(t) * scale); + matrix = glm::translate(matrix, -translation_vector); +} + +void RotateAndScaleDepthByWidth::interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + glm::vec3 translation_vector(SlideWidthScale*origin.x, SlideHeightScale*origin.y, SlideWidthScale*origin.z); + glm::vec3 scale_vector(SlideWidthScale * SlideWidthScale, SlideHeightScale * SlideHeightScale, 1); + matrix = glm::translate(matrix, translation_vector); + if (scale) + matrix = glm::scale(matrix, scale_vector); + matrix = glm::rotate(matrix, static_cast<float>(t*angle), axis); + if (scale) + matrix = glm::scale(matrix, 1.f / scale_vector); + matrix = glm::translate(matrix, -translation_vector); +} + +void RotateAndScaleDepthByHeight::interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + glm::vec3 translation_vector(SlideWidthScale*origin.x, SlideHeightScale*origin.y, SlideHeightScale*origin.z); + glm::vec3 scale_vector(SlideWidthScale * SlideWidthScale, SlideHeightScale * SlideHeightScale, 1); + matrix = glm::translate(matrix, translation_vector); + if (scale) + matrix = glm::scale(matrix, scale_vector); + matrix = glm::rotate(matrix, static_cast<float>(t*angle), axis); + if (scale) + matrix = glm::scale(matrix, 1.f / scale_vector); + matrix = glm::translate(matrix, -translation_vector); +} + +SEllipseTranslate::SEllipseTranslate(double dWidth, double dHeight, double dStartPosition, + double dEndPosition, bool bInter, double T0, double T1): + Operation(bInter, T0, T1) +{ + width = dWidth; + height = dHeight; + startPosition = dStartPosition; + endPosition = dEndPosition; +} + +void SEllipseTranslate::interpolate(glm::mat4& matrix, double t, double /* SlideWidthScale */, double /* SlideHeightScale */) const +{ + if(t <= mnT0) + return; + if(!mbInterpolate || t > mnT1) + t = mnT1; + t = intervalInter(t,mnT0,mnT1); + + double a1, a2, x, y; + a1 = startPosition*2*M_PI; + a2 = (startPosition + t*(endPosition - startPosition))*2*M_PI; + x = width*(cos (a2) - cos (a1))/2; + y = height*(sin (a2) - sin (a1))/2; + + matrix = glm::translate(matrix, glm::vec3(x, 0, y)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/Operation.hxx b/slideshow/source/engine/opengl/Operation.hxx new file mode 100644 index 000000000..0ee818f24 --- /dev/null +++ b/slideshow/source/engine/opengl/Operation.hxx @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2015 by Collabora, Ltd. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_OGLTRANS_OPERATIONS_HXX_ +#define INCLUDED_OGLTRANS_OPERATIONS_HXX_ + +#include <config_lgpl.h> +#include <glm/gtc/type_ptr.hpp> + +#include <memory> + +/** This class is to be derived to make any operation (transform) you may need in order to construct your transitions +*/ +class Operation +{ +public: + virtual ~Operation(){} + Operation(const Operation&) = delete; + Operation& operator=(const Operation&) = delete; + +protected: + /** Should this operation be interpolated . If TRUE, the transform will smoothly move from making no difference from t = 0.0 to mnT0 to being completely transformed from t = mnT1 to 1. If FALSE, the transform will be ineffectual from t = 0 to mnT0, and completely transformed from t = mnT0 to 1. + */ + bool mbInterpolate; + + /** time to begin the transformation + */ + double mnT0; + + /** time to finish the transformation + */ + double mnT1; +public: + /** this is the function that is called to give the Operation to OpenGL. + + @param t + time from t = 0 to t = 1 + + @param SlideWidthScale + width of slide divided by width of window + + @param SlideHeightScale + height of slide divided by height of window + + */ + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const = 0; + +protected: + Operation(bool bInterpolate, double nT0, double nT1): + mbInterpolate(bInterpolate), mnT0(nT0), mnT1(nT1){} +}; + +/** this class is a generic CounterClockWise(CCW) rotation with an axis angle +*/ +class SRotate: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + /** Constructor + + @param Axis + axis to rotate about + + @param Origin + position that rotation axis runs through + + @param Angle + angle in degrees of CCW rotation + + @param bInter + see Operation + + @param T0 + transformation starting time + + @param T1 + transformation ending time + + */ + SRotate(const glm::vec3& Axis, const glm::vec3& Origin, double Angle, + bool bInter, double T0, double T1); +private: + /** axis to rotate CCW about + */ + glm::vec3 axis; + + /** position that rotation axis runs through + */ + glm::vec3 origin; + + /** angle in degrees of CCW rotation + */ + double angle; +}; + +std::shared_ptr<SRotate> +makeSRotate(const glm::vec3& Axis, const glm::vec3& Origin, double Angle, + bool bInter, double T0, double T1); + +/** scaling transformation +*/ +class SScale: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + /** Constructor + + @param Scale + amount to scale by + + @param Origin + position that rotation axis runs through + + @param bInter + see Operation + + @param T0 + transformation starting time + + @param T1 + transformation ending time + + */ + SScale(const glm::vec3& Scale, const glm::vec3& Origin,bool bInter, double T0, double T1); +private: + glm::vec3 scale; + glm::vec3 origin; +}; + +std::shared_ptr<SScale> +makeSScale(const glm::vec3& Scale, const glm::vec3& Origin,bool bInter, double T0, double T1); + +/** translation transformation +*/ +class STranslate: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + /** Constructor + + @param Vector + vector to translate + + @param bInter + see Operation + + @param T0 + transformation starting time + + @param T1 + transformation ending time + + */ + STranslate(const glm::vec3& Vector,bool bInter, double T0, double T1); +private: + /** vector to translate by + */ + glm::vec3 vector; +}; + +std::shared_ptr<STranslate> +makeSTranslate(const glm::vec3& Vector,bool bInter, double T0, double T1); + +/** translation transformation +*/ +class SEllipseTranslate: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + /** Constructor + + @param Vector + vector to translate + + @param bInter + see Operation + + @param T0 + transformation starting time + + @param T1 + transformation ending time + + */ + SEllipseTranslate(double dWidth, double dHeight, double dStartPosition, double dEndPosition, bool bInter, double T0, double T1); +private: + /** width and length of the ellipse + */ + double width, height; + + /** start and end position on the ellipse <0,1> + */ + double startPosition; + double endPosition; +}; + +std::shared_ptr<SEllipseTranslate> +makeSEllipseTranslate(double dWidth, double dHeight, double dStartPosition, double dEndPosition, bool bInter, double T0, double T1); + +/** Same as SRotate, except the depth is scaled by the width of the slide divided by the width of the window. +*/ +class RotateAndScaleDepthByWidth: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + RotateAndScaleDepthByWidth(const glm::vec3& Axis,const glm::vec3& Origin,double Angle, bool bScale, bool bInter, double T0, double T1); +private: + glm::vec3 axis; + glm::vec3 origin; + double angle; + bool scale; +}; + +std::shared_ptr<RotateAndScaleDepthByWidth> +makeRotateAndScaleDepthByWidth(const glm::vec3& Axis,const glm::vec3& Origin,double Angle, bool bScale, bool bInter, double T0, double T1); + +/** Same as SRotate, except the depth is scaled by the width of the slide divided by the height of the window. +*/ +class RotateAndScaleDepthByHeight: public Operation +{ +public: + virtual void interpolate(glm::mat4& matrix, double t, double SlideWidthScale, double SlideHeightScale) const override; + + RotateAndScaleDepthByHeight(const glm::vec3& Axis,const glm::vec3& Origin,double Angle, bool bScale, bool bInter, double T0, double T1); +private: + glm::vec3 axis; + glm::vec3 origin; + double angle; + bool scale; +}; + +std::shared_ptr<RotateAndScaleDepthByHeight> +makeRotateAndScaleDepthByHeight(const glm::vec3& Axis,const glm::vec3& Origin,double Angle, bool bScale, bool bInter, double T0, double T1); + +#endif // INCLUDED_SLIDESHOW_OPERATIONS_HXX_ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/TransitionImpl.cxx b/slideshow/source/engine/opengl/TransitionImpl.cxx new file mode 100644 index 000000000..ba43acddc --- /dev/null +++ b/slideshow/source/engine/opengl/TransitionImpl.cxx @@ -0,0 +1,2292 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <sal/log.hxx> + +#include <algorithm> +#include <array> + +#include <comphelper/random.hxx> + +#include "Operation.hxx" +#include "TransitionImpl.hxx" +#include <cmath> + +TransitionScene::TransitionScene(TransitionScene const& rOther) + : maLeavingSlidePrimitives(rOther.maLeavingSlidePrimitives) + , maEnteringSlidePrimitives(rOther.maEnteringSlidePrimitives) + , maOverallOperations(rOther.maOverallOperations) + , maSceneObjects(rOther.maSceneObjects) +{ +} + +TransitionScene& TransitionScene::operator=(const TransitionScene& rOther) +{ + TransitionScene aTmp(rOther); + swap(aTmp); + return *this; +} + +void TransitionScene::swap(TransitionScene& rOther) +{ + using std::swap; + + swap(maLeavingSlidePrimitives, rOther.maLeavingSlidePrimitives); + swap(maEnteringSlidePrimitives, rOther.maEnteringSlidePrimitives); + swap(maOverallOperations, rOther.maOverallOperations); + swap(maSceneObjects, rOther.maSceneObjects); +} + +OGLTransitionImpl::~OGLTransitionImpl() +{ +} + +void OGLTransitionImpl::uploadModelViewProjectionMatrices() +{ + double EyePos(10.0); + double const RealF(1.0); + double const RealN(-1.0); + double const RealL(-1.0); + double RealR(1.0); + double const RealB(-1.0); + double RealT(1.0); + double ClipN(EyePos+5.0*RealN); + double ClipF(EyePos+15.0*RealF); + double ClipL(RealL*8.0); + double ClipR(RealR*8.0); + double ClipB(RealB*8.0); + double ClipT(RealT*8.0); + + glm::mat4 projection = glm::frustum<float>(ClipL, ClipR, ClipB, ClipT, ClipN, ClipF); + //This scaling is to take the plane with BottomLeftCorner(-1,-1,0) and TopRightCorner(1,1,0) and map it to the screen after the perspective division. + glm::vec3 scale(1.0 / (((RealR * 2.0 * ClipN) / (EyePos * (ClipR - ClipL))) - ((ClipR + ClipL) / (ClipR - ClipL))), + 1.0 / (((RealT * 2.0 * ClipN) / (EyePos * (ClipT - ClipB))) - ((ClipT + ClipB) / (ClipT - ClipB))), + 1.0); + projection = glm::scale(projection, scale); + glm::mat4 modelview = glm::translate(glm::mat4(), glm::vec3(0, 0, -EyePos)); + + GLint location = glGetUniformLocation( m_nProgramObject, "u_projectionMatrix" ); + if( location != -1 ) { + glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection)); + CHECK_GL_ERROR(); + } + + location = glGetUniformLocation( m_nProgramObject, "u_modelViewMatrix" ); + if( location != -1 ) { + glUniformMatrix4fv(location, 1, false, glm::value_ptr(modelview)); + CHECK_GL_ERROR(); + } +} + +static std::vector<int> uploadPrimitives(const Primitives_t& primitives) +{ + int size = 0; + for (const Primitive& primitive: primitives) + size += primitive.getVerticesByteSize(); + + CHECK_GL_ERROR(); + glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_STATIC_DRAW); + CHECK_GL_ERROR(); + Vertex *buf = static_cast<Vertex*>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); + + std::vector<int> indices; + int last_pos = 0; + for (const Primitive& primitive: primitives) { + indices.push_back(last_pos); + int num = primitive.writeVertices(buf); + buf += num; + last_pos += num; + } + + CHECK_GL_ERROR(); + glUnmapBuffer(GL_ARRAY_BUFFER); + CHECK_GL_ERROR(); + return indices; +} + +bool OGLTransitionImpl::prepare( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) +{ + m_nProgramObject = makeShader(); + if (!m_nProgramObject) + return false; + + CHECK_GL_ERROR(); + glUseProgram( m_nProgramObject ); + CHECK_GL_ERROR(); + + const SceneObjects_t& rSceneObjects(maScene.getSceneObjects()); + for(size_t i(0); i != rSceneObjects.size(); ++i) { + rSceneObjects[i]->prepare(m_nProgramObject); + } + + GLint location = glGetUniformLocation( m_nProgramObject, "leavingSlideTexture" ); + if( location != -1 ) { + glUniform1i( location, 0 ); // texture unit 0 + CHECK_GL_ERROR(); + } + + location = glGetUniformLocation( m_nProgramObject, "enteringSlideTexture" ); + if( location != -1 ) { + glUniform1i( location, 2 ); // texture unit 2 + CHECK_GL_ERROR(); + } + + uploadModelViewProjectionMatrices(); + + m_nPrimitiveTransformLocation = glGetUniformLocation( m_nProgramObject, "u_primitiveTransformMatrix" ); + m_nSceneTransformLocation = glGetUniformLocation( m_nProgramObject, "u_sceneTransformMatrix" ); + m_nOperationsTransformLocation = glGetUniformLocation( m_nProgramObject, "u_operationsTransformMatrix" ); + m_nTimeLocation = glGetUniformLocation( m_nProgramObject, "time" ); + + glGenVertexArrays(1, &m_nVertexArrayObject); + glBindVertexArray(m_nVertexArrayObject); + + glGenBuffers(1, &m_nVertexBufferObject); + glBindBuffer(GL_ARRAY_BUFFER, m_nVertexBufferObject); + + // In practice both leaving and entering slides share the same primitives. + m_nFirstIndices = uploadPrimitives(getScene().getLeavingSlide()); + + // Attribute bindings + m_nPositionLocation = glGetAttribLocation(m_nProgramObject, "a_position"); + if (m_nPositionLocation != -1) { + glEnableVertexAttribArray(m_nPositionLocation); + glVertexAttribPointer( m_nPositionLocation, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, position)) ); + CHECK_GL_ERROR(); + } + + m_nNormalLocation = glGetAttribLocation(m_nProgramObject, "a_normal"); + if (m_nNormalLocation != -1) { + glEnableVertexAttribArray(m_nNormalLocation); + glVertexAttribPointer( m_nNormalLocation, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, normal)) ); + CHECK_GL_ERROR(); + } + + m_nTexCoordLocation = glGetAttribLocation(m_nProgramObject, "a_texCoord"); + if (m_nTexCoordLocation != -1) { + glEnableVertexAttribArray(m_nTexCoordLocation); + glVertexAttribPointer( m_nTexCoordLocation, 2, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, texcoord)) ); + CHECK_GL_ERROR(); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + CHECK_GL_ERROR(); + + prepareTransition( glLeavingSlideTex, glEnteringSlideTex, pContext ); + return true; +} + +void OGLTransitionImpl::finish() +{ + const SceneObjects_t& rSceneObjects(maScene.getSceneObjects()); + for(size_t i(0); i != rSceneObjects.size(); ++i) { + rSceneObjects[i]->finish(); + } + + finishTransition(); + + CHECK_GL_ERROR(); + if( m_nProgramObject ) { + glDeleteBuffers(1, &m_nVertexBufferObject); + m_nVertexBufferObject = 0; + glDeleteVertexArrays(1, &m_nVertexArrayObject); + m_nVertexArrayObject = 0; + glDeleteProgram( m_nProgramObject ); + m_nProgramObject = 0; + } + CHECK_GL_ERROR(); +} + +void OGLTransitionImpl::prepare( double, double ) +{ +} + +void OGLTransitionImpl::cleanup() +{ +} + +void OGLTransitionImpl::prepareTransition( sal_Int32, sal_Int32, OpenGLContext* ) +{ +} + +void OGLTransitionImpl::finishTransition() +{ +} + +void OGLTransitionImpl::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext * ) +{ + CHECK_GL_ERROR(); + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + + glUniform1f( m_nTimeLocation, nTime ); + + glActiveTexture( GL_TEXTURE2 ); + glBindTexture( GL_TEXTURE_2D, glEnteringSlideTex ); + glActiveTexture( GL_TEXTURE0 ); + + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + CHECK_GL_ERROR(); +} + +void OGLTransitionImpl::display( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, + double SlideWidth, double SlideHeight, double DispWidth, double DispHeight, OpenGLContext *pContext ) +{ + const double SlideWidthScale = SlideWidth/DispWidth; + const double SlideHeightScale = SlideHeight/DispHeight; + + CHECK_GL_ERROR(); + glBindVertexArray(m_nVertexArrayObject); + prepare( SlideWidth, SlideHeight ); + + CHECK_GL_ERROR(); + displaySlides_( nTime, glLeavingSlideTex, glEnteringSlideTex, SlideWidthScale, SlideHeightScale, pContext ); + CHECK_GL_ERROR(); + displayScene( nTime, SlideWidth, SlideHeight, DispWidth, DispHeight ); + CHECK_GL_ERROR(); +} + +void OGLTransitionImpl::applyOverallOperations( double nTime, double SlideWidthScale, double SlideHeightScale ) +{ + const Operations_t& rOverallOperations(maScene.getOperations()); + glm::mat4 matrix; + for(size_t i(0); i != rOverallOperations.size(); ++i) + rOverallOperations[i]->interpolate(matrix, nTime, SlideWidthScale, SlideHeightScale); + CHECK_GL_ERROR(); + if (m_nOperationsTransformLocation != -1) { + glUniformMatrix4fv(m_nOperationsTransformLocation, 1, false, glm::value_ptr(matrix)); + CHECK_GL_ERROR(); + } +} + +static void displayPrimitives(const Primitives_t& primitives, GLint primitiveTransformLocation, double nTime, double WidthScale, double HeightScale, std::vector<int>::const_iterator first) +{ + for (const Primitive& primitive: primitives) + primitive.display(primitiveTransformLocation, nTime, WidthScale, HeightScale, *first++); +} + +void +OGLTransitionImpl::displaySlide( + const double nTime, + const sal_Int32 glSlideTex, const Primitives_t& primitives, + double SlideWidthScale, double SlideHeightScale ) +{ + CHECK_GL_ERROR(); + glBindTexture(GL_TEXTURE_2D, glSlideTex); + CHECK_GL_ERROR(); + if (m_nSceneTransformLocation != -1) { + glUniformMatrix4fv(m_nSceneTransformLocation, 1, false, glm::value_ptr(glm::mat4())); + CHECK_GL_ERROR(); + } + displayPrimitives(primitives, m_nPrimitiveTransformLocation, nTime, SlideWidthScale, SlideHeightScale, m_nFirstIndices.cbegin()); + CHECK_GL_ERROR(); +} + +void +OGLTransitionImpl::displayUnbufferedSlide( + const double nTime, + const sal_Int32 glSlideTex, const Primitives_t& primitives, + double SlideWidthScale, double SlideHeightScale ) +{ + CHECK_GL_ERROR(); + glBindTexture(GL_TEXTURE_2D, glSlideTex); + CHECK_GL_ERROR(); + glBindVertexArray(0); + CHECK_GL_ERROR(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + CHECK_GL_ERROR(); + if (m_nSceneTransformLocation != -1) { + glUniformMatrix4fv(m_nSceneTransformLocation, 1, false, glm::value_ptr(glm::mat4())); + CHECK_GL_ERROR(); + } + for (const Primitive& primitive: primitives) + primitive.display(m_nPrimitiveTransformLocation, nTime, SlideWidthScale, SlideHeightScale); + CHECK_GL_ERROR(); + glBindVertexArray(m_nVertexArrayObject); + CHECK_GL_ERROR(); + glBindBuffer(GL_ARRAY_BUFFER, m_nVertexBufferObject); + CHECK_GL_ERROR(); +} + +void OGLTransitionImpl::displayScene( double nTime, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight ) +{ + const SceneObjects_t& rSceneObjects(maScene.getSceneObjects()); + CHECK_GL_ERROR(); + for(size_t i(0); i != rSceneObjects.size(); ++i) + rSceneObjects[i]->display(m_nSceneTransformLocation, m_nPrimitiveTransformLocation, nTime, SlideWidth, SlideHeight, DispWidth, DispHeight); + CHECK_GL_ERROR(); +} + +void Primitive::display(GLint primitiveTransformLocation, double nTime, double WidthScale, double HeightScale) const +{ + glm::mat4 matrix; + applyOperations( matrix, nTime, WidthScale, HeightScale ); + + CHECK_GL_ERROR(); + if (primitiveTransformLocation != -1) { + glUniformMatrix4fv(primitiveTransformLocation, 1, false, glm::value_ptr(matrix)); + CHECK_GL_ERROR(); + } + + GLuint nVertexArrayObject; + glGenVertexArrays(1, &nVertexArrayObject); + CHECK_GL_ERROR(); + glBindVertexArray(nVertexArrayObject); + CHECK_GL_ERROR(); + + GLuint nBuffer; + glGenBuffers(1, &nBuffer); + CHECK_GL_ERROR(); + glBindBuffer(GL_ARRAY_BUFFER, nBuffer); + CHECK_GL_ERROR(); + glBufferData(GL_ARRAY_BUFFER, getVerticesByteSize(), Vertices.data(), GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + CHECK_GL_ERROR(); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr); + CHECK_GL_ERROR(); + glDrawArrays( GL_TRIANGLES, 0, Vertices.size() ); + CHECK_GL_ERROR(); + + glDeleteBuffers(1, &nBuffer); + CHECK_GL_ERROR(); + + glDeleteVertexArrays(1, &nVertexArrayObject); + CHECK_GL_ERROR(); +} + +void Primitive::display(GLint primitiveTransformLocation, double nTime, double WidthScale, double HeightScale, int first) const +{ + glm::mat4 matrix; + applyOperations( matrix, nTime, WidthScale, HeightScale ); + + CHECK_GL_ERROR(); + if (primitiveTransformLocation != -1) { + glUniformMatrix4fv(primitiveTransformLocation, 1, false, glm::value_ptr(matrix)); + CHECK_GL_ERROR(); + } + glDrawArrays( GL_TRIANGLES, first, Vertices.size() ); + + CHECK_GL_ERROR(); +} + +void Primitive::applyOperations(glm::mat4& matrix, double nTime, double WidthScale, double HeightScale) const +{ + for(const auto & rOperation : Operations) + rOperation->interpolate(matrix, nTime, WidthScale, HeightScale); + matrix = glm::scale(matrix, glm::vec3(WidthScale, HeightScale, 1)); +} + +void SceneObject::display(GLint sceneTransformLocation, GLint primitiveTransformLocation, double nTime, double /* SlideWidth */, double /* SlideHeight */, double DispWidth, double DispHeight ) const +{ + // fixme: allow various model spaces, now we make it so that + // it is regular -1,-1 to 1,1, where the whole display fits in + glm::mat4 matrix; + if (DispHeight > DispWidth) + matrix = glm::scale(matrix, glm::vec3(DispHeight/DispWidth, 1, 1)); + else + matrix = glm::scale(matrix, glm::vec3(1, DispWidth/DispHeight, 1)); + CHECK_GL_ERROR(); + if (sceneTransformLocation != -1) { + glUniformMatrix4fv(sceneTransformLocation, 1, false, glm::value_ptr(matrix)); + CHECK_GL_ERROR(); + } + displayPrimitives(maPrimitives, primitiveTransformLocation, nTime, 1, 1, maFirstIndices.cbegin()); + CHECK_GL_ERROR(); +} + +void SceneObject::pushPrimitive(const Primitive &p) +{ + maPrimitives.push_back(p); +} + +SceneObject::SceneObject() + : maPrimitives() +{ +} + +SceneObject::~SceneObject() +{ +} + +namespace +{ + +class Iris : public SceneObject +{ +public: + Iris() = default; + + virtual void prepare(GLuint program) override; + virtual void display(GLint sceneTransformLocation, GLint primitiveTransformLocation, double nTime, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight) const override; + virtual void finish() override; + +private: + GLuint maTexture = 0; + GLuint maBuffer = 0; + GLuint maVertexArray = 0; +}; + +void Iris::display(GLint sceneTransformLocation, GLint primitiveTransformLocation, double nTime, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight ) const +{ + glBindVertexArray(maVertexArray); + CHECK_GL_ERROR(); + glBindTexture(GL_TEXTURE_2D, maTexture); + CHECK_GL_ERROR(); + SceneObject::display(sceneTransformLocation, primitiveTransformLocation, nTime, SlideWidth, SlideHeight, DispWidth, DispHeight); +} + +void Iris::prepare(GLuint program) +{ + CHECK_GL_ERROR(); + static const GLubyte img[3] = { 80, 80, 80 }; + + glGenTextures(1, &maTexture); + glBindTexture(GL_TEXTURE_2D, maTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, img); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + CHECK_GL_ERROR(); + + glGenVertexArrays(1, &maVertexArray); + glBindVertexArray(maVertexArray); + + glGenBuffers(1, &maBuffer); + glBindBuffer(GL_ARRAY_BUFFER, maBuffer); + maFirstIndices = uploadPrimitives(maPrimitives); + + // Attribute bindings + GLint location = glGetAttribLocation(program, "a_position"); + if (location != -1) { + glEnableVertexAttribArray(location); + glVertexAttribPointer( location, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, position)) ); + CHECK_GL_ERROR(); + } + + location = glGetAttribLocation(program, "a_normal"); + if (location != -1) { + glEnableVertexAttribArray(location); + glVertexAttribPointer( location, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, normal)) ); + CHECK_GL_ERROR(); + } + + location = glGetAttribLocation(program, "a_texCoord"); + if (location != -1) { + glEnableVertexAttribArray(location); + glVertexAttribPointer( location, 2, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, texcoord)) ); + CHECK_GL_ERROR(); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void Iris::finish() +{ + CHECK_GL_ERROR(); + glDeleteBuffers(1, &maBuffer); + CHECK_GL_ERROR(); + glDeleteVertexArrays(1, &maVertexArray); + CHECK_GL_ERROR(); + glDeleteTextures(1, &maTexture); + CHECK_GL_ERROR(); +} + +} + +namespace +{ + +class ReflectionTransition : public OGLTransitionImpl +{ +public: + ReflectionTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : OGLTransitionImpl(rScene, rSettings) + {} + +private: + virtual GLuint makeShader() const override; + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) override; + + virtual void prepareTransition( sal_Int32, sal_Int32, OpenGLContext* ) override { + glDisable(GL_CULL_FACE); + } + + virtual void finishTransition() override { + glEnable(GL_CULL_FACE); + } +}; + +GLuint ReflectionTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "reflectionVertexShader", "reflectionFragmentShader" ); +} + +void ReflectionTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, + double SlideWidthScale, double SlideHeightScale, OpenGLContext * ) +{ + CHECK_GL_ERROR(); + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + + sal_Int32 texture; + Primitives_t slide; + if (nTime < 0.5) { + texture = glLeavingSlideTex; + slide = getScene().getLeavingSlide(); + } else { + texture = glEnteringSlideTex; + slide = getScene().getEnteringSlide(); + } + + displaySlide( nTime, texture, slide, SlideWidthScale, SlideHeightScale ); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeReflectionTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + Operations_t&& rOverallOperations, + const TransitionSettings& rSettings) +{ + return std::make_shared<ReflectionTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives), std::move(rOverallOperations), SceneObjects_t()), + rSettings); +} + +} + +namespace +{ + +class SimpleTransition : public OGLTransitionImpl +{ +public: + SimpleTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : OGLTransitionImpl(rScene, rSettings) + { + } + +private: + virtual GLuint makeShader() const override; + + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) override; +}; + +GLuint SimpleTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "basicFragmentShader" ); +} + +void SimpleTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, + double SlideWidthScale, double SlideHeightScale, OpenGLContext * ) +{ + CHECK_GL_ERROR(); + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + + CHECK_GL_ERROR(); + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeSimpleTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + Operations_t&& rOverallOperations, + SceneObjects_t&& rSceneObjects, + const TransitionSettings& rSettings) +{ + return std::make_shared<SimpleTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives), + std::move(rOverallOperations), std::move(rSceneObjects)), + rSettings); +} + +std::shared_ptr<OGLTransitionImpl> +makeSimpleTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + Operations_t&& rOverallOperations, + const TransitionSettings& rSettings = TransitionSettings()) +{ + return makeSimpleTransition(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives), + std::move(rOverallOperations), SceneObjects_t(), rSettings); +} + +std::shared_ptr<OGLTransitionImpl> +makeSimpleTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + SceneObjects_t&& rSceneObjects, + const TransitionSettings& rSettings) +{ + return makeSimpleTransition(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives), + Operations_t(), std::move(rSceneObjects), rSettings); +} + +std::shared_ptr<OGLTransitionImpl> +makeSimpleTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings = TransitionSettings()) +{ + return makeSimpleTransition(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives), + Operations_t(), SceneObjects_t(), rSettings); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeOutsideCubeFaceToLeft() +{ + Primitive Slide; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,-1),90,false,false,0.0,1.0)); + + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back(Slide); + + Operations_t aOperations; + aOperations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,-1),-90,false,true,0.0,1.0)); + + return makeSimpleTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), std::move(aOperations)); +} + +std::shared_ptr<OGLTransitionImpl> makeInsideCubeFaceToLeft() +{ + Primitive Slide; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,1),-90,false,false,0.0,1.0)); + + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back(Slide); + + Operations_t aOperations; + aOperations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,1),90,false,true,0.0,1.0)); + + return makeSimpleTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), std::move(aOperations)); +} + +std::shared_ptr<OGLTransitionImpl> makeFallLeaving() +{ + Primitive Slide; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(1,0,0),glm::vec3(0,-1,0), 90,true,true,0.0,1.0)); + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back(Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapEntering = false; + + return makeSimpleTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), aSettings); +} + +std::shared_ptr<OGLTransitionImpl> makeTurnAround() +{ + Primitive Slide; + TransitionSettings aSettings; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeSScale(glm::vec3(1, -1, 1), glm::vec3(0, -1.02, 0), false, -1, 0)); + aLeavingPrimitives.push_back(Slide); + + Slide.Operations.clear(); + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,0),-180,true,false,0.0,1.0)); + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeSScale(glm::vec3(1, -1, 1), glm::vec3(0, -1.02, 0), false, -1, 0)); + aEnteringPrimitives.push_back(Slide); + + Operations_t aOperations; + aOperations.push_back(makeSTranslate(glm::vec3(0, 0, -1.5),true, 0, 0.5)); + aOperations.push_back(makeSTranslate(glm::vec3(0, 0, 1.5), true, 0.5, 1)); + aOperations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0, 1, 0),glm::vec3(0, 0, 0), -180, true, true, 0.0, 1.0)); + + return makeReflectionTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), std::move(aOperations), aSettings); +} + +std::shared_ptr<OGLTransitionImpl> makeTurnDown() +{ + Primitive Slide; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back(Slide); + + Slide.Operations.push_back(makeSTranslate(glm::vec3(0, 0, 0.0001), false, -1.0, 0.0)); + Slide.Operations.push_back(makeSRotate (glm::vec3(0, 0, 1), glm::vec3(-1, 1, 0), -90, true, 0.0, 1.0)); + Slide.Operations.push_back(makeSRotate (glm::vec3(0, 0, 1), glm::vec3(-1, 1, 0), 90, false, -1.0, 0.0)); + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back(Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = false; + + return makeSimpleTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), aSettings); +} + +std::shared_ptr<OGLTransitionImpl> makeIris() +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aEnteringPrimitives; + aEnteringPrimitives.push_back (Slide); + + Slide.Operations.push_back (makeSTranslate (glm::vec3 (0, 0, 0.000001), false, -1, 0)); + Slide.Operations.push_back (makeSTranslate (glm::vec3 (0, 0, -0.000002), false, 0.5, 1)); + Primitives_t aLeavingPrimitives; + aLeavingPrimitives.push_back (Slide); + + + Primitive irisPart; + int i, nSteps = 24, nParts = 7; + double t = 1.0/nSteps, lx = 1, ly = 0, of=2.2, f=1.42; + + for (i=1; i<=nSteps; i++) { + double x = cos ((3*2*M_PI*t)/nParts); + double y = -sin ((3*2*M_PI*t)/nParts); + double cx = (f*x + 1)/2; + double cy = (f*y + 1)/2; + double lcx = (f*lx + 1)/2; + double lcy = (f*ly + 1)/2; + double cxo = (of*x + 1)/2; + double cyo = (of*y + 1)/2; + double lcxo = (of*lx + 1)/2; + double lcyo = (of*ly + 1)/2; + irisPart.pushTriangle (glm::vec2 (lcx, lcy), + glm::vec2 (lcxo, lcyo), + glm::vec2 (cx, cy)); + irisPart.pushTriangle (glm::vec2 (cx, cy), + glm::vec2 (lcxo, lcyo), + glm::vec2 (cxo, cyo)); + lx = x; + ly = y; + t += 1.0/nSteps; + } + + std::shared_ptr<Iris> pIris = std::make_shared<Iris>(); + double angle = 87; + + for (i = 0; i < nParts; i++) { + irisPart.Operations.clear (); + double rx, ry; + + rx = cos ((2*M_PI*i)/nParts); + ry = sin ((2*M_PI*i)/nParts); + irisPart.Operations.push_back (makeSRotate (glm::vec3(0, 0, 1), glm::vec3(rx, ry, 0), angle, true, 0.0, 0.5)); + irisPart.Operations.push_back (makeSRotate (glm::vec3(0, 0, 1), glm::vec3(rx, ry, 0), -angle, true, 0.5, 1)); + if (i > 0) { + irisPart.Operations.push_back (makeSTranslate (glm::vec3(rx, ry, 0), false, -1, 0)); + irisPart.Operations.push_back (makeSRotate (glm::vec3(0, 0, 1), glm::vec3(0, 0, 0), i*360.0/nParts, false, -1, 0)); + irisPart.Operations.push_back (makeSTranslate (glm::vec3(-1, 0, 0), false, -1, 0)); + } + irisPart.Operations.push_back(makeSTranslate(glm::vec3(0, 0, 1), false, -2, 0.0)); + irisPart.Operations.push_back (makeSRotate (glm::vec3(1, .5, 0), glm::vec3(1, 0, 0), -30, false, -1, 0)); + pIris->pushPrimitive (irisPart); + } + + SceneObjects_t aSceneObjects; + aSceneObjects.push_back (pIris); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeSimpleTransition(std::move(aLeavingPrimitives), std::move(aEnteringPrimitives), std::move(aSceneObjects), aSettings); +} + +namespace +{ + +class RochadeTransition : public ReflectionTransition +{ +public: + RochadeTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : ReflectionTransition(rScene, rSettings) + {} + +private: + virtual void displaySlides_(double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext) override; +}; + +void RochadeTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext * ) +{ + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + + if( nTime > .5) { + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + } else { + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + } +} + +std::shared_ptr<OGLTransitionImpl> +makeRochadeTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + return std::make_shared<RochadeTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings) + ; + +} +} + +std::shared_ptr<OGLTransitionImpl> makeRochade() +{ + Primitive Slide; + TransitionSettings aSettings; + + double w, h; + + w = 2.2; + h = 10; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + + Slide.Operations.push_back(makeSEllipseTranslate(w, h, 0.25, -0.25, true, 0, 1)); + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,0), -45, true, true, 0, 1)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back(Slide); + + Slide.Operations.push_back(makeSScale(glm::vec3(1, -1, 1), glm::vec3(0, -1.02, 0), false, -1, 0)); + aLeavingSlide.push_back(Slide); + + Slide.Operations.clear(); + Slide.Operations.push_back(makeSEllipseTranslate(w, h, 0.75, 0.25, true, 0, 1)); + Slide.Operations.push_back(makeSTranslate(glm::vec3(0, 0, -h), false, -1, 0)); + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,0), -45, true, true, 0, 1)); + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0,1,0),glm::vec3(0,0,0), 45, true, false, -1, 0)); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back(Slide); + + Slide.Operations.push_back(makeSScale(glm::vec3(1, -1, 1), glm::vec3(0, -1.02, 0), false, -1, 0)); + aEnteringSlide.push_back(Slide); + + return makeRochadeTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings); +} + +static double randFromNeg1to1() +{ + return comphelper::rng::uniform_real_distribution(-1.0, std::nextafter(1.0, DBL_MAX)); +} + +// TODO(Q3): extract to basegfx +static glm::vec3 randNormVectorInXYPlane() +{ + glm::vec3 toReturn(randFromNeg1to1(),randFromNeg1to1(),0.0); + return glm::normalize(toReturn); +} + +template<typename T> +static T clamp(const T& rIn) +{ + return glm::clamp(rIn, T(-1.0), T(1.0)); +} + +std::shared_ptr<OGLTransitionImpl> makeRevolvingCircles( sal_uInt16 nCircles , sal_uInt16 nPointsOnCircles ) +{ + double dAngle(2*M_PI/static_cast<double>( nPointsOnCircles )); + if(nCircles < 2 || nPointsOnCircles < 4) + return makeNByMTileFlip(1,1); + float Radius(1.0/static_cast<double>( nCircles )); + float dRadius(Radius); + float LastRadius(0.0); + float NextRadius(2*Radius); + + /// now we know there is at least two circles + /// the first will always be a full circle + /// the last will always be the outer shell of the slide with a circle hole + + //add the full circle + std::vector<glm::vec2> unScaledTexCoords; + float TempAngle(0.0); + for(unsigned int Point(0); Point < nPointsOnCircles; ++Point) + { + unScaledTexCoords.emplace_back( cos(TempAngle - M_PI_2) , sin(TempAngle- M_PI_2) ); + + TempAngle += dAngle; + } + + Primitives_t aLeavingSlide; + Primitives_t aEnteringSlide; + { + Primitive EnteringSlide; + Primitive LeavingSlide; + for(int Point(0); Point + 1 < nPointsOnCircles; ++Point) + { + EnteringSlide.pushTriangle( glm::vec2( 0.5 , 0.5 ) , Radius * unScaledTexCoords[ Point + 1 ] / 2.0f + glm::vec2( 0.5 , 0.5 ) , Radius * unScaledTexCoords[ Point ] / 2.0f + glm::vec2( 0.5 , 0.5 ) ); + LeavingSlide.pushTriangle( glm::vec2( 0.5 , 0.5 ) , Radius * unScaledTexCoords[ Point + 1 ] / 2.0f + glm::vec2( 0.5 , 0.5 ) , Radius * unScaledTexCoords[ Point ] / 2.0f + glm::vec2( 0.5, 0.5) ); + } + EnteringSlide.pushTriangle( glm::vec2(0.5,0.5) , Radius * unScaledTexCoords[ 0 ] / 2.0f + glm::vec2( 0.5 , 0.5 ) , Radius * unScaledTexCoords[ nPointsOnCircles - 1 ] / 2.0f + glm::vec2( 0.5 , 0.5 ) ); + LeavingSlide.pushTriangle( glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) ); + + glm::vec3 axis(randNormVectorInXYPlane()); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, Radius/2.0 , (NextRadius + 1)/2.0 ) ); + LeavingSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, Radius/2.0 , (NextRadius + 1)/2.0 ) ); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , -180, false,0.0,1.0) ); + + aEnteringSlide.push_back(EnteringSlide); + aLeavingSlide.push_back(LeavingSlide); + LastRadius = Radius; + Radius = NextRadius; + NextRadius += dRadius; + } + + for(int i(1); i < nCircles - 1; ++i) + { + Primitive LeavingSlide; + Primitive EnteringSlide; + for(int Side(0); Side < nPointsOnCircles - 1; ++Side) + { + EnteringSlide.pushTriangle(Radius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + EnteringSlide.pushTriangle(Radius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + + LeavingSlide.pushTriangle(Radius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + LeavingSlide.pushTriangle(Radius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + } + + EnteringSlide.pushTriangle(Radius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + EnteringSlide.pushTriangle(Radius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + + LeavingSlide.pushTriangle(Radius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + LeavingSlide.pushTriangle(Radius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) , Radius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + + glm::vec3 axis(randNormVectorInXYPlane()); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, Radius/2.0 , (NextRadius + 1)/2.0 ) ); + LeavingSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, Radius/2.0 , (NextRadius + 1)/2.0 ) ); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , -180, false,0.0,1.0) ); + + aEnteringSlide.push_back(EnteringSlide); + aLeavingSlide.push_back(LeavingSlide); + + LastRadius = Radius; + Radius = NextRadius; + NextRadius += dRadius; + } + { + Radius = sqrt(2.0); + Primitive LeavingSlide; + Primitive EnteringSlide; + for(int Side(0); Side < nPointsOnCircles - 1; ++Side) + { + + EnteringSlide.pushTriangle(clamp(Radius*unScaledTexCoords[Side])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + EnteringSlide.pushTriangle(clamp(Radius*unScaledTexCoords[Side])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) , clamp(Radius*unScaledTexCoords[Side + 1])/2.0f + glm::vec2(0.5,0.5) ); + + LeavingSlide.pushTriangle(clamp(Radius*unScaledTexCoords[Side])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) ); + LeavingSlide.pushTriangle(clamp(Radius*unScaledTexCoords[Side])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[Side + 1]/2.0f + glm::vec2(0.5,0.5) , clamp(Radius*unScaledTexCoords[Side + 1])/2.0f + glm::vec2(0.5,0.5) ); + } + + EnteringSlide.pushTriangle(clamp(Radius*unScaledTexCoords[nPointsOnCircles - 1])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + EnteringSlide.pushTriangle(clamp(Radius*unScaledTexCoords[nPointsOnCircles - 1])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) , clamp(Radius*unScaledTexCoords[0])/2.0f + glm::vec2(0.5,0.5) ); + + LeavingSlide.pushTriangle(clamp(Radius*unScaledTexCoords[nPointsOnCircles - 1])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[nPointsOnCircles - 1]/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) ); + LeavingSlide.pushTriangle(clamp(Radius*unScaledTexCoords[nPointsOnCircles - 1])/2.0f + glm::vec2(0.5,0.5) , LastRadius*unScaledTexCoords[0]/2.0f + glm::vec2(0.5,0.5) , clamp(Radius*unScaledTexCoords[0])/2.0f + glm::vec2(0.5,0.5) ); + + glm::vec3 axis(randNormVectorInXYPlane()); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, (LastRadius + dRadius)/2.0 , 1.0 ) ); + LeavingSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , 180, true, (LastRadius + dRadius)/2.0 , 1.0 ) ); + EnteringSlide.Operations.push_back( makeSRotate( axis , glm::vec3(0,0,0) , -180, false,0.0,1.0) ); + + aEnteringSlide.push_back(EnteringSlide); + aLeavingSlide.push_back(LeavingSlide); + } + + return makeSimpleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide)); +} + +std::shared_ptr<OGLTransitionImpl> makeHelix( sal_uInt16 nRows ) +{ + double invN(1.0/static_cast<double>(nRows)); + double iDn = 0.0; + double iPDn = invN; + Primitives_t aLeavingSlide; + Primitives_t aEnteringSlide; + for(unsigned int i(0); i < nRows; ++i) + { + Primitive Tile; + + Tile.pushTriangle(glm::vec2( 1.0 , iDn ) , glm::vec2( 0.0 , iDn ) , glm::vec2( 0.0 , iPDn )); + + Tile.pushTriangle(glm::vec2( 1.0 , iPDn ) , glm::vec2( 1.0 , iDn ) , glm::vec2( 0.0 , iPDn )); + + Tile.Operations.push_back( makeSRotate( glm::vec3( 0 , 1 , 0 ) , ( Tile.getVertex(1) + Tile.getVertex(3) )/2.0f , 180 , + true, std::min(std::max(static_cast<double>(i - nRows/2.0)*invN/2.0,0.0),1.0), + std::min(std::max(static_cast<double>(i + nRows/2.0)*invN/2.0,0.0),1.0) ) ); + + aLeavingSlide.push_back(Tile); + + Tile.Operations.push_back( makeSRotate( glm::vec3( 0 , 1 , 0 ) , ( Tile.getVertex(1) + Tile.getVertex(3) )/2.0f , -180 , false,0.0,1.0) ); + + aEnteringSlide.push_back(Tile); + + iDn += invN; + iPDn += invN; + } + + return makeSimpleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide)); +} + +static float fdiv(int a, int b) +{ + return static_cast<float>(a)/b; +} + +static glm::vec2 vec(float x, float y, float nx, float ny) +{ + x = x < 0.0 ? 0.0 : x; + x = std::min(x, nx); + y = y < 0.0 ? 0.0 : y; + y = std::min(y, ny); + return glm::vec2(fdiv(x, nx), fdiv(y, ny)); +} + +std::shared_ptr<OGLTransitionImpl> makeNByMTileFlip( sal_uInt16 n, sal_uInt16 m ) +{ + Primitives_t aLeavingSlide; + Primitives_t aEnteringSlide; + + for (int x = 0; x < n; x++) + { + for (int y = 0; y < n; y++) + { + Primitive aTile; + glm::vec2 x11 = vec(x, y, n, m); + glm::vec2 x12 = vec(x, y+1, n, m); + glm::vec2 x21 = vec(x+1, y, n, m); + glm::vec2 x22 = vec(x+1, y+1, n, m); + + aTile.pushTriangle(x21, x11, x12); + aTile.pushTriangle(x22, x21, x12); + + aTile.Operations.push_back(makeSRotate( glm::vec3(0 , 1, 0), (aTile.getVertex(1) + aTile.getVertex(3)) / 2.0f, 180 , true, x11.x * x11.y / 2.0f , ((x22.x * x22.y) + 1.0f) / 2.0f)); + aLeavingSlide.push_back(aTile); + + aTile.Operations.push_back(makeSRotate( glm::vec3(0 , 1, 0), (aTile.getVertex(1) + aTile.getVertex(3)) / 2.0f, -180, false, x11.x * x11.y / 2.0f , ((x22.x * x22.y) + 1.0f) / 2.0f)); + aEnteringSlide.push_back(aTile); + } + } + + return makeSimpleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide)); +} + +Primitive& Primitive::operator=(const Primitive& rvalue) +{ + Primitive aTmp(rvalue); + swap(aTmp); + return *this; +} + +Primitive::Primitive(const Primitive& rvalue) + : Operations(rvalue.Operations) + , Vertices(rvalue.Vertices) +{ +} + +void Primitive::swap(Primitive& rOther) +{ + using std::swap; + + swap(Operations, rOther.Operations); + swap(Vertices, rOther.Vertices); +} + +void Primitive::pushTriangle(const glm::vec2& SlideLocation0,const glm::vec2& SlideLocation1,const glm::vec2& SlideLocation2) +{ + std::vector<glm::vec3> Verts; + std::vector<glm::vec2> Texs; + Verts.reserve(3); + Texs.reserve(3); + + Verts.emplace_back( 2*SlideLocation0.x - 1, -2*SlideLocation0.y + 1 , 0.0 ); + Verts.emplace_back( 2*SlideLocation1.x - 1, -2*SlideLocation1.y + 1 , 0.0 ); + Verts.emplace_back( 2*SlideLocation2.x - 1, -2*SlideLocation2.y + 1 , 0.0 ); + + //figure out if they're facing the correct way, and make them face the correct way. + glm::vec3 Normal( glm::cross( Verts[0] - Verts[1] , Verts[1] - Verts[2] ) ); + if(Normal.z >= 0.0)//if the normal is facing us + { + Texs.push_back(SlideLocation0); + Texs.push_back(SlideLocation1); + Texs.push_back(SlideLocation2); + } + else // if the normal is facing away from us, make it face us + { + Texs.push_back(SlideLocation0); + Texs.push_back(SlideLocation2); + Texs.push_back(SlideLocation1); + Verts.clear(); + Verts.emplace_back( 2*SlideLocation0.x - 1, -2*SlideLocation0.y + 1 , 0.0 ); + Verts.emplace_back( 2*SlideLocation2.x - 1, -2*SlideLocation2.y + 1 , 0.0 ); + Verts.emplace_back( 2*SlideLocation1.x - 1, -2*SlideLocation1.y + 1 , 0.0 ); + } + + Vertices.push_back({Verts[0], glm::vec3(0, 0, 1), Texs[0]}); //all normals always face the screen when untransformed. + Vertices.push_back({Verts[1], glm::vec3(0, 0, 1), Texs[1]}); //all normals always face the screen when untransformed. + Vertices.push_back({Verts[2], glm::vec3(0, 0, 1), Texs[2]}); //all normals always face the screen when untransformed. +} + +namespace +{ + +class DiamondTransition : public SimpleTransition +{ +public: + DiamondTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : SimpleTransition(rScene, rSettings) + {} + +private: + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) override; +}; + +Primitives_t makeLeavingSlide(double nTime) +{ + Primitive Slide2; + if( nTime >= 0.5 ) { + double m = 1 - nTime; + + Slide2.pushTriangle (glm::vec2 (0,0), glm::vec2 (m,0), glm::vec2 (0,m)); + Slide2.pushTriangle (glm::vec2 (nTime,0), glm::vec2 (1,0), glm::vec2 (1,m)); + Slide2.pushTriangle (glm::vec2 (1,nTime), glm::vec2 (1,1), glm::vec2 (nTime,1)); + Slide2.pushTriangle (glm::vec2 (0,nTime), glm::vec2 (m,1), glm::vec2 (0,1)); + } else { + double l = 0.5 - nTime; + double h = 0.5 + nTime; + + Slide2.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0.5,l)); + Slide2.pushTriangle (glm::vec2 (0.5,l), glm::vec2 (1,0), glm::vec2 (h,0.5)); + Slide2.pushTriangle (glm::vec2 (1,0), glm::vec2 (1,1), glm::vec2 (h,0.5)); + Slide2.pushTriangle (glm::vec2 (h,0.5), glm::vec2 (1,1), glm::vec2 (0.5,h)); + Slide2.pushTriangle (glm::vec2 (0.5,h), glm::vec2 (1,1), glm::vec2 (0,1)); + Slide2.pushTriangle (glm::vec2 (l,0.5), glm::vec2 (0.5,h), glm::vec2 (0,1)); + Slide2.pushTriangle (glm::vec2 (0,0), glm::vec2 (l,0.5), glm::vec2 (0,1)); + Slide2.pushTriangle (glm::vec2 (0,0), glm::vec2 (0.5,l), glm::vec2 (l,0.5)); + } + Slide2.Operations.push_back (makeSTranslate (glm::vec3 (0, 0, 0.00000001), false, -1, 0)); + Primitives_t aLeavingSlidePrimitives; + aLeavingSlidePrimitives.push_back (Slide2); + + return aLeavingSlidePrimitives; +} + +void DiamondTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, + double SlideWidthScale, double SlideHeightScale, OpenGLContext * ) +{ + CHECK_GL_ERROR(); + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + + CHECK_GL_ERROR(); + displayUnbufferedSlide( nTime, glLeavingSlideTex, makeLeavingSlide(nTime), SlideWidthScale, SlideHeightScale ); + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeDiamondTransition(const TransitionSettings& rSettings) +{ + Primitive Slide1; + Slide1.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide1.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aEnteringSlidePrimitives; + aEnteringSlidePrimitives.push_back (Slide1); + Primitives_t aLeavingSlidePrimitives; + aLeavingSlidePrimitives.push_back (Slide1); + return std::make_shared<DiamondTransition>(TransitionScene(std::move(aLeavingSlidePrimitives), std::move(aEnteringSlidePrimitives)), rSettings); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeDiamond() +{ + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeDiamondTransition(aSettings); +} + +std::shared_ptr<OGLTransitionImpl> makeVenetianBlinds( bool vertical, int parts ) +{ + static double t30 = tan( M_PI/6.0 ); + double ln = 0; + double p = 1.0/parts; + + Primitives_t aLeavingSlide; + Primitives_t aEnteringSlide; + for( int i=0; i<parts; i++ ) { + Primitive Slide; + double n = (i + 1)/static_cast<double>(parts); + if( vertical ) { + Slide.pushTriangle (glm::vec2 (ln,0), glm::vec2 (n,0), glm::vec2 (ln,1)); + Slide.pushTriangle (glm::vec2 (n,0), glm::vec2 (ln,1), glm::vec2 (n,1)); + Slide.Operations.push_back(makeRotateAndScaleDepthByWidth(glm::vec3(0, 1, 0), glm::vec3(n + ln - 1, 0, -t30*p), -120, true, true, 0.0, 1.0)); + } else { + Slide.pushTriangle (glm::vec2 (0,ln), glm::vec2 (1,ln), glm::vec2 (0,n)); + Slide.pushTriangle (glm::vec2 (1,ln), glm::vec2 (0,n), glm::vec2 (1,n)); + Slide.Operations.push_back(makeRotateAndScaleDepthByHeight(glm::vec3(1, 0, 0), glm::vec3(0, 1 - n - ln, -t30*p), -120, true, true, 0.0, 1.0)); + } + aLeavingSlide.push_back (Slide); + + if( vertical ) { + Slide.Operations.push_back(makeSRotate(glm::vec3(0, 1, 0), glm::vec3(2*n - 1, 0, 0), -60, false, -1, 0)); + Slide.Operations.push_back(makeSRotate(glm::vec3(0, 1, 0), glm::vec3(n + ln - 1, 0, 0), 180, false, -1, 0)); + } else { + Slide.Operations.push_back(makeSRotate(glm::vec3(1, 0, 0), glm::vec3(0, 1 - 2*n, 0), -60, false, -1, 0)); + Slide.Operations.push_back(makeSRotate(glm::vec3(1, 0, 0), glm::vec3(0, 1 - n - ln, 0), 180, false, -1, 0)); + } + aEnteringSlide.push_back (Slide); + ln = n; + } + + return makeSimpleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide)); +} + +namespace +{ + +class FadeSmoothlyTransition : public OGLTransitionImpl +{ +public: + FadeSmoothlyTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : OGLTransitionImpl(rScene, rSettings) + {} + +private: + virtual GLuint makeShader() const override; +}; + +GLuint FadeSmoothlyTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "fadeFragmentShader" ); +} + +std::shared_ptr<OGLTransitionImpl> +makeFadeSmoothlyTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + return std::make_shared<FadeSmoothlyTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings) + ; +} + +} + +std::shared_ptr<OGLTransitionImpl> makeFadeSmoothly() +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeFadeSmoothlyTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings); +} + +namespace +{ + +class FadeThroughColorTransition : public OGLTransitionImpl +{ +public: + FadeThroughColorTransition(const TransitionScene& rScene, const TransitionSettings& rSettings, bool white) + : OGLTransitionImpl(rScene, rSettings), useWhite( white ) + {} + +private: + virtual GLuint makeShader() const override; + bool useWhite; +}; + +GLuint FadeThroughColorTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "fadeBlackFragmentShader", + useWhite ? "#define use_white" : "", "" ); +} + +std::shared_ptr<OGLTransitionImpl> +makeFadeThroughColorTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings, + bool white) +{ + return std::make_shared<FadeThroughColorTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings, white) + ; +} + +} + +std::shared_ptr<OGLTransitionImpl> makeFadeThroughColor( bool white ) +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeFadeThroughColorTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings, white); +} + +namespace +{ + +class PermTextureTransition : public OGLTransitionImpl +{ +protected: + PermTextureTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : OGLTransitionImpl(rScene, rSettings) + , m_nHelperTexture(0) + {} + + virtual void finishTransition() override; + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) override; + +private: + /** various data */ + GLuint m_nHelperTexture; +}; + +void PermTextureTransition::finishTransition() +{ + CHECK_GL_ERROR(); + if ( m_nHelperTexture ) + { + glDeleteTextures( 1, &m_nHelperTexture ); + m_nHelperTexture = 0; + } + CHECK_GL_ERROR(); +} + +constexpr auto permutation2D = []() constexpr { + int permutation256 [256]= { + 215, 100, 200, 204, 233, 50, 85, 196, + 71, 141, 122, 160, 93, 131, 243, 234, + 162, 183, 36, 155, 4, 62, 35, 205, + 40, 102, 33, 27, 255, 55, 214, 156, + 75, 163, 134, 126, 249, 74, 197, 228, + 72, 90, 206, 235, 17, 22, 49, 169, + 227, 89, 16, 5, 117, 60, 248, 230, + 217, 68, 138, 96, 194, 170, 136, 10, + 112, 238, 184, 189, 176, 42, 225, 212, + 84, 58, 175, 244, 150, 168, 219, 236, + 101, 208, 123, 37, 164, 110, 158, 201, + 78, 114, 57, 48, 70, 142, 106, 43, + 232, 26, 32, 252, 239, 98, 191, 94, + 59, 149, 39, 187, 203, 190, 19, 13, + 133, 45, 61, 247, 23, 34, 20, 52, + 118, 209, 146, 193, 222, 18, 1, 152, + 46, 41, 91, 148, 115, 25, 135, 77, + 254, 147, 224, 161, 9, 213, 223, 250, + 231, 251, 127, 166, 63, 179, 81, 130, + 139, 28, 120, 151, 241, 86, 111, 0, + 88, 153, 172, 182, 159, 105, 178, 47, + 51, 167, 65, 66, 92, 73, 198, 211, + 245, 195, 31, 220, 140, 76, 221, 186, + 154, 185, 56, 83, 38, 165, 109, 67, + 124, 226, 132, 53, 229, 29, 12, 181, + 121, 24, 207, 199, 177, 113, 30, 80, + 3, 97, 188, 79, 216, 173, 8, 145, + 87, 128, 180, 237, 240, 137, 125, 104, + 15, 242, 119, 246, 103, 143, 95, 144, + 2, 44, 69, 157, 192, 174, 14, 54, + 218, 82, 64, 210, 11, 6, 129, 21, + 116, 171, 99, 202, 7, 107, 253, 108 + }; + std::array<unsigned char, 256 * 256> a{}; + for (int y = 0; y < 256; y++) + for (int x = 0; x < 256; x++) + a[x + y * 256] = permutation256[(y + permutation256[x]) & 0xff]; + return a; +}(); + +void initPermTexture(GLuint *texID) +{ + CHECK_GL_ERROR(); + glGenTextures(1, texID); + glBindTexture(GL_TEXTURE_2D, *texID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RED, GL_UNSIGNED_BYTE, + permutation2D.data()); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); +} + +void PermTextureTransition::prepareTransition( sal_Int32, sal_Int32, OpenGLContext* ) +{ + CHECK_GL_ERROR(); + GLint location = glGetUniformLocation( m_nProgramObject, "permTexture" ); + if( location != -1 ) { + glActiveTexture(GL_TEXTURE1); + CHECK_GL_ERROR(); + if( !m_nHelperTexture ) + initPermTexture( &m_nHelperTexture ); + + glActiveTexture(GL_TEXTURE0); + CHECK_GL_ERROR(); + + glUniform1i( location, 1 ); // texture unit 1 + CHECK_GL_ERROR(); + } + CHECK_GL_ERROR(); +} + +} + +namespace +{ + +class StaticNoiseTransition : public PermTextureTransition +{ +public: + StaticNoiseTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : PermTextureTransition(rScene, rSettings) + {} + +private: + virtual GLuint makeShader() const override; +}; + +GLuint StaticNoiseTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "staticFragmentShader" ); +} + +std::shared_ptr<OGLTransitionImpl> +makeStaticNoiseTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + return std::make_shared<StaticNoiseTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings) + ; +} + +} + +std::shared_ptr<OGLTransitionImpl> makeStatic() +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeStaticNoiseTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings); +} + +namespace +{ + +class DissolveTransition : public PermTextureTransition +{ +public: + DissolveTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : PermTextureTransition(rScene, rSettings) + {} + +private: + virtual GLuint makeShader() const override; +}; + +GLuint DissolveTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "dissolveFragmentShader" ); +} + +std::shared_ptr<OGLTransitionImpl> +makeDissolveTransition( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + return std::make_shared<DissolveTransition>( + TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings) + ; +} + +} + +std::shared_ptr<OGLTransitionImpl> makeDissolve() +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeDissolveTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings); +} + +namespace +{ + +class VortexTransition : public PermTextureTransition +{ +public: + VortexTransition(const TransitionScene& rScene, const TransitionSettings& rSettings, int nNX, int nNY) + : PermTextureTransition(rScene, rSettings) + , maNumTiles(nNX,nNY) + { + mvTileInfo.resize(6*maNumTiles.x*maNumTiles.y); + mnFramebuffers[0] = 0; + mnFramebuffers[1] = 0; + mnDepthTextures[0] = 0; + mnDepthTextures[1] = 0; + } + +private: + virtual void finishTransition() override; + virtual GLuint makeShader() const override; + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) override; + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) override; + + GLint mnSlideLocation = -1; + GLint mnTileInfoLocation = -1; + GLuint mnTileInfoBuffer = 0u; + GLint mnShadowLocation = -1; + std::array<GLuint, 2> mnFramebuffers; + std::array<GLuint, 2> mnDepthTextures; + + glm::ivec2 maNumTiles; + + std::vector<GLfloat> mvTileInfo; +}; + +void VortexTransition::finishTransition() +{ + PermTextureTransition::finishTransition(); + + CHECK_GL_ERROR(); + glDeleteTextures(2, mnDepthTextures.data()); + mnDepthTextures = {0u, 0u}; + CHECK_GL_ERROR(); + glDeleteFramebuffers(2, mnFramebuffers.data()); + mnFramebuffers = {0u, 0u}; + glDeleteBuffers(1, &mnTileInfoBuffer); + mnTileInfoBuffer = 0u; + mnSlideLocation = -1; + mnTileInfoLocation = -1; + mnShadowLocation = -1; + CHECK_GL_ERROR(); +} + +GLuint VortexTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader" ); +} + +glm::mat4 lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up) { + glm::vec3 f = glm::normalize(center - eye); + glm::vec3 u = glm::normalize(up); + glm::vec3 s = glm::normalize(glm::cross(f, u)); + u = glm::cross(s, f); + + return glm::mat4(s.x, u.x, -f.x, 0, + s.y, u.y, -f.y, 0, + s.z, u.z, -f.z, 0, + -glm::dot(s, eye), -glm::dot(u, eye), glm::dot(f, eye), 1); +} + +void VortexTransition::prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) +{ + CHECK_GL_ERROR(); + PermTextureTransition::prepareTransition( glLeavingSlideTex, glEnteringSlideTex, pContext ); + CHECK_GL_ERROR(); + + mnSlideLocation = glGetUniformLocation(m_nProgramObject, "slide"); + mnTileInfoLocation = glGetAttribLocation(m_nProgramObject, "tileInfo"); + GLint nNumTilesLocation = glGetUniformLocation(m_nProgramObject, "numTiles"); + mnShadowLocation = glGetUniformLocation(m_nProgramObject, "shadow"); + GLint nOrthoProjectionMatrix = glGetUniformLocation(m_nProgramObject, "orthoProjectionMatrix"); + GLint nOrthoViewMatrix = glGetUniformLocation(m_nProgramObject, "orthoViewMatrix"); + GLint location = glGetUniformLocation(m_nProgramObject, "leavingShadowTexture"); + glUniform1i(location, 2); + location = glGetUniformLocation(m_nProgramObject, "enteringShadowTexture"); + glUniform1i(location, 3); + CHECK_GL_ERROR(); + + glUniform2iv(nNumTilesLocation, 1, glm::value_ptr(maNumTiles)); + CHECK_GL_ERROR(); + + glGenBuffers(1, &mnTileInfoBuffer); + CHECK_GL_ERROR(); + + // We store the (x,y) indexes of the tile each vertex belongs to in a float, so they must fit. + assert(maNumTiles.x < 256); + assert(maNumTiles.y < 256); + + // Two triangles, i.e. six vertices, per tile + { + int n = 0; + for (int x = 0; x < maNumTiles.x; x++) + { + for (int y = 0; y < maNumTiles.y; y++) + { + for (int v = 0; v < 6; v++) + { + mvTileInfo[n] = x + (y << 8) + (v << 16); + n++; + } + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, mnTileInfoBuffer); + CHECK_GL_ERROR(); + glEnableVertexAttribArray(mnTileInfoLocation); + CHECK_GL_ERROR(); + glVertexAttribPointer(mnTileInfoLocation, 1, GL_FLOAT, GL_FALSE, 0, nullptr); + CHECK_GL_ERROR(); + glBufferData(GL_ARRAY_BUFFER, mvTileInfo.size()*sizeof(GLfloat), mvTileInfo.data(), GL_STATIC_DRAW); + CHECK_GL_ERROR(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + CHECK_GL_ERROR(); + + double EyePos(10.0); + double const RealF(1.0); + double const RealN(-1.0); + double const RealL(-2.0); + double RealR(2.0); + double const RealB(-2.0); + double RealT(2.0); + double ClipN(EyePos+5.0*RealN); + double ClipF(EyePos+15.0*RealF); + double ClipL(RealL*8.0); + double ClipR(RealR*8.0); + double ClipB(RealB*8.0); + double ClipT(RealT*8.0); + + glm::mat4 projection = glm::ortho<float>(ClipL, ClipR, ClipB, ClipT, ClipN, ClipF); + //This scaling is to take the plane with BottomLeftCorner(-1,-1,0) and TopRightCorner(1,1,0) and map it to the screen after the perspective division. + glm::vec3 scale(1.0 / (((RealR * 2.0 * ClipN) / (EyePos * (ClipR - ClipL))) - ((ClipR + ClipL) / (ClipR - ClipL))), + 1.0 / (((RealT * 2.0 * ClipN) / (EyePos * (ClipT - ClipB))) - ((ClipT + ClipB) / (ClipT - ClipB))), + 1.0); + projection = glm::scale(projection, scale); + glUniformMatrix4fv(nOrthoProjectionMatrix, 1, false, glm::value_ptr(projection)); + + glm::mat4 view = lookAt(glm::vec3(-1, 1, EyePos), glm::vec3(-0.5, 0.5, 0), glm::vec3(0, 1, 0)); + glUniformMatrix4fv(nOrthoViewMatrix, 1, false, glm::value_ptr(view)); + + // Generate the framebuffers and textures for the shadows. + glGenTextures(2, mnDepthTextures.data()); + glGenFramebuffers(2, mnFramebuffers.data()); + + for (int i : {0, 1}) { + glBindTexture(GL_TEXTURE_2D, mnDepthTextures[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 2048, 2048, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebuffers[i]); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, mnDepthTextures[i], 0); + glDrawBuffer(GL_NONE); // No color buffer is drawn to. + + // Always check that our framebuffer is ok + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + SAL_WARN("slideshow.opengl", "Wrong framebuffer!"); + return; + } + } + + pContext->restoreDefaultFramebuffer(); + glBindTexture(GL_TEXTURE_2D, 0); + + glActiveTexture( GL_TEXTURE2 ); + glBindTexture( GL_TEXTURE_2D, mnDepthTextures[0] ); + glActiveTexture( GL_TEXTURE3 ); + glBindTexture( GL_TEXTURE_2D, mnDepthTextures[1] ); + glActiveTexture( GL_TEXTURE0 ); +} + +void VortexTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext * pContext ) +{ + CHECK_GL_ERROR(); + applyOverallOperations( nTime, SlideWidthScale, SlideHeightScale ); + glUniform1f( m_nTimeLocation, nTime ); + glUniform1f( mnShadowLocation, 1.0 ); + + std::array<GLint, 4> viewport; + glGetIntegerv(GL_VIEWPORT, viewport.data()); + glViewport(0, 0, 2048, 2048); + + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebuffers[0]); + glClear(GL_DEPTH_BUFFER_BIT); + glUniform1f( mnSlideLocation, 0.0 ); + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebuffers[1]); + glClear(GL_DEPTH_BUFFER_BIT); + glUniform1f( mnSlideLocation, 1.0 ); + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + pContext->restoreDefaultFramebuffer(); + glUniform1f( mnShadowLocation, 0.0 ); + glUniform1f( mnSlideLocation, 0.0 ); + displaySlide( nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale ); + glUniform1f( mnSlideLocation, 1.0 ); + displaySlide( nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale ); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeVortexTransition(Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings, + int NX, + int NY) +{ + return std::make_shared<VortexTransition>(TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings, + NX, NY); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeVortex() +{ + const int NX = 96, NY = 96; + Primitive Slide; + + for (int x = 0; x < NX; x++) + { + for (int y = 0; y < NY; y++) + { + Slide.pushTriangle (glm::vec2 (fdiv(x,NX),fdiv(y,NY)), glm::vec2 (fdiv(x+1,NX),fdiv(y,NY)), glm::vec2 (fdiv(x,NX),fdiv(y+1,NY))); + Slide.pushTriangle (glm::vec2 (fdiv(x+1,NX),fdiv(y,NY)), glm::vec2 (fdiv(x,NX),fdiv(y+1,NY)), glm::vec2 (fdiv(x+1,NX),fdiv(y+1,NY))); + } + } + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + aSettings.mnRequiredGLVersion = 3.2f; + + return makeVortexTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings, NX, NY); +} + +namespace +{ + +class RippleTransition : public OGLTransitionImpl +{ +public: + RippleTransition(const TransitionScene& rScene, const TransitionSettings& rSettings, const glm::vec2& rCenter) + : OGLTransitionImpl(rScene, rSettings), + maCenter(rCenter) + { + } + +private: + virtual GLuint makeShader() const override; + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) override; + virtual void prepare( double SlideWidth, double SlideHeight ) override; + + glm::vec2 maCenter; + GLint maSlideRatioLocation = -1; +}; + +GLuint RippleTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "basicVertexShader", "rippleFragmentShader" ); +} + +void RippleTransition::prepareTransition( sal_Int32, sal_Int32, OpenGLContext* ) +{ + GLint nCenterLocation = glGetUniformLocation(m_nProgramObject, "center"); + CHECK_GL_ERROR(); + + glUniform2fv(nCenterLocation, 1, glm::value_ptr(maCenter)); + CHECK_GL_ERROR(); + + maSlideRatioLocation = glGetUniformLocation(m_nProgramObject, "slideRatio"); + CHECK_GL_ERROR(); +} + +void RippleTransition::prepare( double SlideWidth, double SlideHeight ) +{ + if( maSlideRatioLocation != -1 ) + glUniform1f( maSlideRatioLocation, SlideWidth / SlideHeight ); +} + +std::shared_ptr<OGLTransitionImpl> +makeRippleTransition(Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + // The center point should be adjustable by the user, but we have no way to do that in the UI + return std::make_shared<RippleTransition>(TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings, + glm::vec2(0.5, 0.5)); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeRipple() +{ + Primitive Slide; + + Slide.pushTriangle (glm::vec2 (0,0), glm::vec2 (1,0), glm::vec2 (0,1)); + Slide.pushTriangle (glm::vec2 (1,0), glm::vec2 (0,1), glm::vec2 (1,1)); + + Primitives_t aLeavingSlide; + aLeavingSlide.push_back (Slide); + + Primitives_t aEnteringSlide; + aEnteringSlide.push_back (Slide); + + TransitionSettings aSettings; + aSettings.mbUseMipMapLeaving = aSettings.mbUseMipMapEntering = false; + + return makeRippleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), aSettings); +} + +static void createHexagon(Primitive& aHexagon, const int x, const int y, const int NX, const int NY) +{ + if (y % 4 == 0) + { + aHexagon.pushTriangle(vec(x-1, y-1, NX, NY), vec(x, y-2, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x, y-2, NX, NY), vec(x+1, y-1, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x+1, y-1, NX, NY), vec(x+1, y, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x+1, y, NX, NY), vec(x, y+1, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x, y+1, NX, NY), vec(x-1, y, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x-1, y, NX, NY), vec(x-1, y-1, NX, NY), vec(x, y+0.5, NX, NY)); + } + else + { + aHexagon.pushTriangle(vec(x-2, y-1, NX, NY), vec(x-1, y-2, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x-1, y-2, NX, NY), vec(x, y-1, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x, y-1, NX, NY), vec(x, y, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x, y, NX, NY), vec(x-1, y+1, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x-1, y+1, NX, NY), vec(x-2, y, NX, NY), vec(x, y+0.5, NX, NY)); + aHexagon.pushTriangle(vec(x-2, y, NX, NY), vec(x-2, y-1, NX, NY), vec(x, y+0.5, NX, NY)); + } +} + +namespace +{ + +class GlitterTransition : public PermTextureTransition +{ +public: + GlitterTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : PermTextureTransition(rScene, rSettings) + { + } + +private: + virtual GLuint makeShader() const override; + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) override; + virtual void cleanup() override; + + GLuint maBuffer = 0; +}; + +GLuint GlitterTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "glitterVertexShader", "glitterFragmentShader" ); +} + +struct ThreeFloats +{ + GLfloat x, y, z; +}; + +void GlitterTransition::prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) +{ + CHECK_GL_ERROR(); + PermTextureTransition::prepareTransition( glLeavingSlideTex, glEnteringSlideTex, pContext ); + CHECK_GL_ERROR(); + + GLint nNumTilesLocation = glGetUniformLocation(m_nProgramObject, "numTiles"); + if (nNumTilesLocation != -1) { + glUniform2iv(nNumTilesLocation, 1, glm::value_ptr(glm::ivec2(41, 41 * 4 / 3))); + CHECK_GL_ERROR(); + } + + glGenBuffers(1, &maBuffer); + glBindBuffer(GL_ARRAY_BUFFER, maBuffer); + + // Upload the center of each hexagon. + const Primitive& primitive = getScene().getLeavingSlide()[0]; + std::vector<ThreeFloats> vertices; + for (int i = 2; i < primitive.getVerticesCount(); i += 18) { + const glm::vec3& center = primitive.getVertex(i); + for (int j = 0; j < 18; ++j) + vertices.push_back({center.x, center.y, center.z}); + } + glBufferData(GL_ARRAY_BUFFER, vertices.size() * 3 * sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); + + GLint location = glGetAttribLocation(m_nProgramObject, "center"); + if (location != -1) { + glEnableVertexAttribArray(location); + glVertexAttribPointer( location, 3, GL_FLOAT, false, 0, nullptr ); + CHECK_GL_ERROR(); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void GlitterTransition::cleanup() +{ + CHECK_GL_ERROR(); + glDeleteBuffers(1, &maBuffer); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeGlitterTransition(Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + return std::make_shared<GlitterTransition>(TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeGlitter() +{ + const int NX = 80; + const int NY = NX * 4 / 3; + + Primitives_t aSlide; + Primitives_t aEmptySlide; + Primitive aHexagon; + + for (int y = 0; y < NY+2; y+=2) + for (int x = 0; x < NX+2; x+=2) + createHexagon(aHexagon, x, y, NX, NY); + + aSlide.push_back(aHexagon); + + return makeGlitterTransition(std::move(aSlide), std::move(aEmptySlide), TransitionSettings()); +} + +namespace +{ + +class HoneycombTransition : public PermTextureTransition +{ +public: + HoneycombTransition(const TransitionScene& rScene, const TransitionSettings& rSettings) + : PermTextureTransition(rScene, rSettings) + { + mnDepthTextures[0] = 0; + mnDepthTextures[1] = 0; + } + +private: + virtual void finishTransition() override; + virtual GLuint makeShader() const override; + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) override; + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) override; + + GLint maHexagonSizeLocation = -1; + GLint maSelectedTextureLocation = -1; + GLint mnShadowLocation = -1; + GLuint mnFramebuffer = 0u; + std::array<GLuint, 2> mnDepthTextures; +}; + +void HoneycombTransition::finishTransition() +{ + PermTextureTransition::finishTransition(); + + CHECK_GL_ERROR(); + glActiveTexture( GL_TEXTURE2 ); + glBindTexture( GL_TEXTURE_2D, 0 ); + glActiveTexture( GL_TEXTURE3 ); + glBindTexture( GL_TEXTURE_2D, 0 ); + glActiveTexture( GL_TEXTURE0 ); + CHECK_GL_ERROR(); + glDeleteTextures(2, mnDepthTextures.data()); + mnDepthTextures = {0u, 0u}; + CHECK_GL_ERROR(); + glDeleteFramebuffers(1, &mnFramebuffer); + mnFramebuffer = 0u; + CHECK_GL_ERROR(); +} + +GLuint HoneycombTransition::makeShader() const +{ + return OpenGLHelper::LoadShaders( "honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader" ); +} + +void HoneycombTransition::prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ) +{ + CHECK_GL_ERROR(); + PermTextureTransition::prepareTransition( glLeavingSlideTex, glEnteringSlideTex, pContext ); + + CHECK_GL_ERROR(); + maHexagonSizeLocation = glGetUniformLocation(m_nProgramObject, "hexagonSize"); + maSelectedTextureLocation = glGetUniformLocation( m_nProgramObject, "selectedTexture" ); + mnShadowLocation = glGetUniformLocation(m_nProgramObject, "shadow"); + GLint nOrthoProjectionMatrix = glGetUniformLocation(m_nProgramObject, "orthoProjectionMatrix"); + GLint nOrthoViewMatrix = glGetUniformLocation(m_nProgramObject, "orthoViewMatrix"); + GLint location = glGetUniformLocation(m_nProgramObject, "colorShadowTexture"); + glUniform1i(location, 2); + location = glGetUniformLocation(m_nProgramObject, "depthShadowTexture"); + glUniform1i(location, 3); + CHECK_GL_ERROR(); + + // We want to see the entering slide behind the leaving one. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + CHECK_GL_ERROR(); + + double EyePos(10.0); + double const RealF(1.0); + double const RealN(-1.0); + double const RealL(-4.0); + double RealR(4.0); + double const RealB(-4.0); + double RealT(4.0); + double ClipN(EyePos+5.0*RealN); + double ClipF(EyePos+15.0*RealF); + double ClipL(RealL*8.0); + double ClipR(RealR*8.0); + double ClipB(RealB*8.0); + double ClipT(RealT*8.0); + + glm::mat4 projection = glm::ortho<float>(ClipL, ClipR, ClipB, ClipT, ClipN, ClipF); + //This scaling is to take the plane with BottomLeftCorner(-1,-1,0) and TopRightCorner(1,1,0) and map it to the screen after the perspective division. + glm::vec3 scale(1.0 / (((RealR * 2.0 * ClipN) / (EyePos * (ClipR - ClipL))) - ((ClipR + ClipL) / (ClipR - ClipL))), + 1.0 / (((RealT * 2.0 * ClipN) / (EyePos * (ClipT - ClipB))) - ((ClipT + ClipB) / (ClipT - ClipB))), + 1.0); + projection = glm::scale(projection, scale); + glUniformMatrix4fv(nOrthoProjectionMatrix, 1, false, glm::value_ptr(projection)); + + glm::mat4 view = lookAt(glm::vec3(0, 0, EyePos), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); + glUniformMatrix4fv(nOrthoViewMatrix, 1, false, glm::value_ptr(view)); + + // Generate the framebuffer and textures for the shadows. + glGenTextures(2, mnDepthTextures.data()); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, mnDepthTextures[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2048, 2048, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, mnDepthTextures[1]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 2048, 2048, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glActiveTexture(GL_TEXTURE0); + glGenFramebuffers(1, &mnFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebuffer); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mnDepthTextures[0], 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, mnDepthTextures[1], 0); + + // Always check that our framebuffer is ok + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + SAL_WARN("slideshow.opengl", "Wrong framebuffer!"); + return; + } + + pContext->restoreDefaultFramebuffer(); +} + +void HoneycombTransition::displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, + double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ) +{ + CHECK_GL_ERROR(); + applyOverallOperations(nTime, SlideWidthScale, SlideHeightScale); + glUniform1f(m_nTimeLocation, nTime); + glUniform1f(mnShadowLocation, 1.0); + CHECK_GL_ERROR(); + + const float borderSize = 0.15f; + + std::array<GLint, 4> viewport; + glGetIntegerv(GL_VIEWPORT, viewport.data()); + glViewport(0, 0, 2048, 2048); + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebuffer); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUniform1f(mnShadowLocation, 1.0); + glUniform1f(maSelectedTextureLocation, 1.0); + glUniform1f(maHexagonSizeLocation, 1.0f - borderSize); + displaySlide(nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale); + glUniform1f(maHexagonSizeLocation, 1.0f + borderSize); + displaySlide(nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale); + + // The back (entering) slide needs to be drawn before the front (leaving) one in order for blending to work. + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + pContext->restoreDefaultFramebuffer(); + glUniform1f(mnShadowLocation, 0.0); + glUniform1f(maSelectedTextureLocation, 0.0); + glUniform1f(maHexagonSizeLocation, 1.0f - borderSize); + displaySlide(nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale); + glUniform1f(maHexagonSizeLocation, 1.0f + borderSize); + displaySlide(nTime, glEnteringSlideTex, getScene().getEnteringSlide(), SlideWidthScale, SlideHeightScale); + glUniform1f(maSelectedTextureLocation, 1.0); + glUniform1f(maHexagonSizeLocation, 1.0f - borderSize); + displaySlide(nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale); + glUniform1f(maHexagonSizeLocation, 1.0f + borderSize); + displaySlide(nTime, glLeavingSlideTex, getScene().getLeavingSlide(), SlideWidthScale, SlideHeightScale); + CHECK_GL_ERROR(); +} + +std::shared_ptr<OGLTransitionImpl> +makeHoneycombTransition(Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + const TransitionSettings& rSettings) +{ + // The center point should be adjustable by the user, but we have no way to do that in the UI + return std::make_shared<HoneycombTransition>(TransitionScene(std::move(rLeavingSlidePrimitives), std::move(rEnteringSlidePrimitives)), + rSettings); +} + +} + +std::shared_ptr<OGLTransitionImpl> makeHoneycomb() +{ + const int NX = 21; + const int NY = 21; + + TransitionSettings aSettings; + aSettings.mnRequiredGLVersion = 3.2f; + + Primitives_t aSlide; + Primitive aHexagon; + for (int y = 0; y < NY+2; y+=2) + for (int x = 0; x < NX+2; x+=2) + aHexagon.pushTriangle(glm::vec2((y % 4) ? fdiv(x, NX) : fdiv(x + 1, NX), fdiv(y, NY)), glm::vec2(1, 0), glm::vec2(0, 0)); + aSlide.push_back(aHexagon); + + return makeHoneycombTransition(std::vector(aSlide), std::vector(aSlide), aSettings); +} + +std::shared_ptr<OGLTransitionImpl> makeNewsflash() +{ + Primitive Slide; + + Slide.pushTriangle(glm::vec2(0,0),glm::vec2(1,0),glm::vec2(0,1)); + Slide.pushTriangle(glm::vec2(1,0),glm::vec2(0,1),glm::vec2(1,1)); + Slide.Operations.push_back(makeSRotate(glm::vec3(0,0,1),glm::vec3(0,0,0),3000,true,0,0.5)); + Slide.Operations.push_back(makeSScale(glm::vec3(0.01,0.01,0.01),glm::vec3(0,0,0),true,0,0.5)); + Slide.Operations.push_back(makeSTranslate(glm::vec3(-10000, 0, 0),false, 0.5, 2)); + Primitives_t aLeavingSlide; + aLeavingSlide.push_back(Slide); + + Slide.Operations.clear(); + Slide.Operations.push_back(makeSRotate(glm::vec3(0,0,1),glm::vec3(0,0,0),-3000,true,0.5,1)); + Slide.Operations.push_back(makeSTranslate(glm::vec3(-100, 0, 0),false, -1, 1)); + Slide.Operations.push_back(makeSTranslate(glm::vec3(100, 0, 0),false, 0.5, 1)); + Slide.Operations.push_back(makeSScale(glm::vec3(0.01,0.01,0.01),glm::vec3(0,0,0),false,-1,1)); + Slide.Operations.push_back(makeSScale(glm::vec3(100,100,100),glm::vec3(0,0,0),true,0.5,1)); + Primitives_t aEnteringSlide; + aEnteringSlide.push_back(Slide); + + Operations_t aOverallOperations; + aOverallOperations.push_back(makeSRotate(glm::vec3(0,0,1),glm::vec3(0.2,0.2,0),1080,true,0,1)); + + return makeSimpleTransition(std::move(aLeavingSlide), std::move(aEnteringSlide), std::move(aOverallOperations)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/TransitionImpl.hxx b/slideshow/source/engine/opengl/TransitionImpl.hxx new file mode 100644 index 000000000..9ca35c1e8 --- /dev/null +++ b/slideshow/source/engine/opengl/TransitionImpl.hxx @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_OGLTRANS_TRANSITIONIMPL_HXX_ +#define INCLUDED_OGLTRANS_TRANSITIONIMPL_HXX_ + +#include <config_lgpl.h> +#include <epoxy/gl.h> +#include <glm/gtc/type_ptr.hpp> + +#include <o3tl/safeint.hxx> +#include <sal/types.h> + +#include <limits> +#include <memory> +#include <vector> + +class Primitive; +class OpenGLContext; +class Operation; +class SceneObject; +class TransitionData; + +struct TransitionSettings +{ + TransitionSettings() : + mbUseMipMapLeaving( true ), + mbUseMipMapEntering( true ), + mnRequiredGLVersion( 3.0f ) + { + } + + /** Whether to use mipmapping for slides textures + */ + bool mbUseMipMapLeaving; + bool mbUseMipMapEntering; + + /** which GL version does the transition require + */ + float mnRequiredGLVersion; +}; + +typedef std::vector<Primitive> Primitives_t; +typedef std::vector<std::shared_ptr<SceneObject> > SceneObjects_t; +typedef std::vector<std::shared_ptr<Operation> > Operations_t; + +class TransitionScene +{ +public: + inline TransitionScene( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + Operations_t&& rOverallOperations = Operations_t(), + SceneObjects_t&& rSceneObjects = SceneObjects_t() + ); + + inline ~TransitionScene(); + + TransitionScene(TransitionScene const& rOther); + TransitionScene& operator=(const TransitionScene& rOther); + + void swap(TransitionScene& rOther); + + const Primitives_t& getLeavingSlide() const + { + return maLeavingSlidePrimitives; + } + + const Primitives_t& getEnteringSlide() const + { + return maEnteringSlidePrimitives; + } + + const Operations_t& getOperations() const + { + return maOverallOperations; + } + + const SceneObjects_t& getSceneObjects() const + { + return maSceneObjects; + } + +private: + /** All the primitives that use the leaving slide texture + */ + Primitives_t maLeavingSlidePrimitives; + + /** All the primitives that use the leaving slide texture + */ + Primitives_t maEnteringSlidePrimitives; + + /** All the operations that should be applied to both leaving and entering slide primitives. These operations will be called in the order they were pushed back in. In OpenGL this effectively uses the operations in the opposite order they were pushed back. + */ + Operations_t maOverallOperations; + + /** All the surrounding scene objects + */ + SceneObjects_t maSceneObjects; +}; + +/** OpenGL 3D Transition class. It implicitly is constructed from XOGLTransition + + It holds Primitives and Operations on those primitives. +*/ +class OGLTransitionImpl +{ +public: + virtual ~OGLTransitionImpl(); + + OGLTransitionImpl(const OGLTransitionImpl&) = delete; + OGLTransitionImpl& operator=(const OGLTransitionImpl&) = delete; + + /** Prepare transition. + */ + bool prepare( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ); + /** Display a step of the transition. + */ + void display( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight, OpenGLContext *pContext ); + /** Clean up after transition. + */ + void finish(); + + TransitionSettings const& getSettings() const + { + return maSettings; + } + +protected: + OGLTransitionImpl(const TransitionScene& rScene, const TransitionSettings& rSettings) + : maScene(rScene) + , maSettings(rSettings) + {} + + TransitionScene const& getScene() const + { + return maScene; + } + + void displaySlide( double nTime, sal_Int32 glSlideTex, const Primitives_t& primitives, double SlideWidthScale, double SlideHeightScale ); + void displayUnbufferedSlide( double nTime, sal_Int32 glSlideTex, const Primitives_t& primitives, double SlideWidthScale, double SlideHeightScale ); + void displayScene( double nTime, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight); + void applyOverallOperations( double nTime, double SlideWidthScale, double SlideHeightScale ); + +private: + /** This function is called in display method to prepare the slides, scene, etc. + * + * Default implementation does nothing. + */ + virtual void prepare( double SlideWidth, double SlideHeight ); + + /** This function is called in display method to prepare the slides, scene, etc. + * + * Default implementation does nothing. + */ + virtual void cleanup(); + + /** This function is called after glx context is ready to let the transition prepare GL related things, like GLSL program. + * + * Default implementation does nothing. + */ + virtual void prepareTransition( sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, OpenGLContext *pContext ); + + /** This function is called when the transition needs to clear after itself, like delete own textures etc. + * + * Default implementation does nothing. + */ + virtual void finishTransition(); + + /** This function is called in display method to display the slides. + * + * Default implementation applies overall operations and then + * displays both slides. + */ + virtual void displaySlides_( double nTime, sal_Int32 glLeavingSlideTex, sal_Int32 glEnteringSlideTex, double SlideWidthScale, double SlideHeightScale, OpenGLContext *pContext ); + + /** This function is called in prepare method to create the GL program. + * + * It is a pure virtual to make sure no class will use a default one. + */ + virtual GLuint makeShader() const = 0; + +private: + const TransitionScene maScene; + const TransitionSettings maSettings; + + /** Calculates the projection and model/view matrices, and upload them. + */ + void uploadModelViewProjectionMatrices(); + + /** Uniform locations for transform matrices + */ + GLint m_nPrimitiveTransformLocation = -1; + GLint m_nSceneTransformLocation = -1; + GLint m_nOperationsTransformLocation = -1; + + /** Per-vertex attribute locations + */ + GLint m_nPositionLocation = -1; + GLint m_nNormalLocation = -1; + GLint m_nTexCoordLocation = -1; + + GLuint m_nVertexArrayObject = 0u; + + std::vector<int> m_nFirstIndices; + +protected: + /** GLSL program object + */ + GLuint m_nProgramObject = 0u; + + /** VBO in which to put primitive data + */ + GLuint m_nVertexBufferObject = 0u; + + /** Location of the "time" uniform + */ + GLint m_nTimeLocation = -1; +}; + + +// "Constructors" of available transitions +std::shared_ptr<OGLTransitionImpl> makeOutsideCubeFaceToLeft(); +std::shared_ptr<OGLTransitionImpl> makeInsideCubeFaceToLeft(); +std::shared_ptr<OGLTransitionImpl> makeNByMTileFlip( sal_uInt16 n, sal_uInt16 m ); +std::shared_ptr<OGLTransitionImpl> makeRevolvingCircles( sal_uInt16 nCircles , sal_uInt16 nPointsOnCircles ); +std::shared_ptr<OGLTransitionImpl> makeHelix( sal_uInt16 nRows ); +std::shared_ptr<OGLTransitionImpl> makeFallLeaving(); +std::shared_ptr<OGLTransitionImpl> makeTurnAround(); +std::shared_ptr<OGLTransitionImpl> makeTurnDown(); +std::shared_ptr<OGLTransitionImpl> makeIris(); +std::shared_ptr<OGLTransitionImpl> makeRochade(); +std::shared_ptr<OGLTransitionImpl> makeVenetianBlinds( bool vertical, int parts ); +std::shared_ptr<OGLTransitionImpl> makeStatic(); +std::shared_ptr<OGLTransitionImpl> makeDissolve(); +std::shared_ptr<OGLTransitionImpl> makeVortex(); +std::shared_ptr<OGLTransitionImpl> makeRipple(); +std::shared_ptr<OGLTransitionImpl> makeGlitter(); +std::shared_ptr<OGLTransitionImpl> makeHoneycomb(); +std::shared_ptr<OGLTransitionImpl> makeNewsflash(); + +/** 2D replacements */ + +std::shared_ptr<OGLTransitionImpl> makeDiamond(); +std::shared_ptr<OGLTransitionImpl> makeFadeSmoothly(); +// fade through black or white +std::shared_ptr<OGLTransitionImpl> makeFadeThroughColor( bool white = false ); + +class SceneObject +{ +public: + SceneObject(); + virtual ~SceneObject(); + SceneObject(const SceneObject&) = delete; + SceneObject& operator=(const SceneObject&) = delete; + + virtual void prepare(GLuint /* program */) {} + virtual void display(GLint sceneTransformLocation, GLint primitiveTransformLocation, double nTime, double SlideWidth, double SlideHeight, double DispWidth, double DispHeight ) const; + virtual void finish() {} + + void pushPrimitive (const Primitive &p); + +protected: + /** All the surrounding scene primitives + */ + Primitives_t maPrimitives; + std::vector<int> maFirstIndices; +}; + +struct Vertex +{ + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texcoord; +}; +static_assert(sizeof(Vertex) == (3 + 3 + 2) * 4, "Vertex struct has wrong size/alignment"); + +/** This class is a list of Triangles that will share Operations, and could possibly share +*/ +class Primitive +{ +public: + Primitive() {} + // making copy constructor explicit makes the class un-suitable for use with stl containers + Primitive(const Primitive& rvalue); + Primitive& operator=(const Primitive& rvalue); + + void swap(Primitive& rOther); + + void applyOperations(glm::mat4& matrix, double nTime, double SlideWidthScale, double SlideHeightScale) const; + void display(GLint primitiveTransformLocation, double nTime, double WidthScale, double HeightScale) const; + void display(GLint primitiveTransformLocation, double nTime, double WidthScale, double HeightScale, int first) const; + + /** PushBack a vertex,normal, and tex coord. Each SlideLocation is where on the slide is mapped to this location ( from (0,0) to (1,1) ). This will make sure the correct aspect ratio is used, and helps to make slides begin and end at the correct position. (0,0) is the top left of the slide, and (1,1) is the bottom right. + + @param SlideLocation0 + Location of first Vertex on slide + + @param SlideLocation1 + Location of second Vertex on slide + + @param SlideLocation2 + Location of third Vertex on slide + + */ + void pushTriangle(const glm::vec2& SlideLocation0,const glm::vec2& SlideLocation1,const glm::vec2& SlideLocation2); + + /** guards against directly changing the vertices + + @return + the list of vertices + */ + const glm::vec3& getVertex(int n) const {return Vertices[n].position;} + + int getVerticesCount() const + { + assert(Vertices.size() < o3tl::make_unsigned(std::numeric_limits<int>::max())); + return int(unsigned(Vertices.size())); + } + + /** accessor for the size of the vertices data + + @return + the size in bytes of the Vertices data + */ + int getVerticesByteSize() const {return Vertices.size() * sizeof(Vertex);} + + /** copies all vertices to the C array passed + + @return + the number of written vertices + */ + int writeVertices(Vertex *location) const { + std::copy(Vertices.begin(), Vertices.end(), location); + return Vertices.size(); + } + + /** list of Operations to be performed on this primitive.These operations will be called in the order they were pushed back in. In OpenGL this effectively uses the operations in the opposite order they were pushed back. + + @return + the list of Operations + + */ + Operations_t Operations; + +private: + /** list of vertices + */ + std::vector<Vertex> Vertices; +}; + +TransitionScene::TransitionScene( + Primitives_t&& rLeavingSlidePrimitives, + Primitives_t&& rEnteringSlidePrimitives, + Operations_t&& rOverallOperations, + SceneObjects_t&& rSceneObjects +) + : maLeavingSlidePrimitives(std::move(rLeavingSlidePrimitives)) + , maEnteringSlidePrimitives(std::move(rEnteringSlidePrimitives)) + , maOverallOperations(std::move(rOverallOperations)) + , maSceneObjects(std::move(rSceneObjects)) +{ +} + +TransitionScene::~TransitionScene() = default; + +#endif // INCLUDED_SLIDESHOW_TRANSITION_HXX_ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/TransitionerImpl.cxx b/slideshow/source/engine/opengl/TransitionerImpl.cxx new file mode 100644 index 000000000..faef77988 --- /dev/null +++ b/slideshow/source/engine/opengl/TransitionerImpl.cxx @@ -0,0 +1,1346 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/types.h> + +#include <memory> + +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/ColorComponentTag.hpp> +#include <com/sun/star/rendering/ColorSpaceType.hpp> +#include <com/sun/star/rendering/RenderingIntent.hpp> +#include <com/sun/star/util/Endianness.hpp> +#include <com/sun/star/animations/TransitionType.hpp> +#undef IN +#undef OUT +#include <com/sun/star/animations/TransitionSubType.hpp> +#include <com/sun/star/presentation/XTransitionFactory.hpp> +#include <com/sun/star/presentation/XTransition.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/geometry/IntegerSize2D.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> + +#include <canvas/canvastools.hxx> + +#include <tools/diagnose_ex.h> + +#include <vcl/canvastools.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/syschild.hxx> +#include <vcl/window.hxx> + +#include "TransitionImpl.hxx" + +#if OSL_DEBUG_LEVEL > 0 +#include <chrono> +#endif + +using namespace ::com::sun::star; +using ::com::sun::star::beans::XFastPropertySet; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; + +namespace +{ + +typedef cppu::WeakComponentImplHelper<presentation::XTransition> OGLTransitionerImplBase; + +#if OSL_DEBUG_LEVEL > 0 +class TimerContext +{ +public: + explicit TimerContext(OUString const& rWhat) + : m_aWhat(rWhat) + , m_StartTime(std::chrono::steady_clock::now()) + { + } + ~TimerContext() + { + auto const aDuration(std::chrono::steady_clock::now() - m_StartTime); + SAL_INFO("slideshow.opengl", m_aWhat << " took: " << std::chrono::duration_cast<std::chrono::microseconds>(aDuration).count()); + } +private: + OUString const m_aWhat; + std::chrono::steady_clock::time_point const m_StartTime; +}; +#endif + +struct OGLFormat +{ + GLint nInternalFormat; + GLenum eFormat; + GLenum eType; +}; + +/* channel ordering: (0:rgba, 1:bgra, 2:argb, 3:abgr) + */ +int calcComponentOrderIndex(const uno::Sequence<sal_Int8>& rTags) +{ + using namespace rendering::ColorComponentTag; + + static const sal_Int8 aOrderTable[] = + { + RGB_RED, RGB_GREEN, RGB_BLUE, ALPHA, + RGB_BLUE, RGB_GREEN, RGB_RED, ALPHA, + ALPHA, RGB_RED, RGB_GREEN, RGB_BLUE, + ALPHA, RGB_BLUE, RGB_GREEN, RGB_RED, + }; + + const sal_Int32 nNumComps(rTags.getLength()); + const sal_Int8* pLine=aOrderTable; + for(int i=0; i<4; ++i) + { + int j=0; + while( j<4 && j<nNumComps && pLine[j] == rTags[j] ) + ++j; + + // all of the line passed, this is a match! + if( j==nNumComps ) + return i; + + pLine+=4; + } + + return -1; +} + +/** This is the Transitioner class for OpenGL 3D transitions in + * slideshow. This class is implicitly + * constructed from XTransitionFactory. +*/ +class OGLTransitionerImpl : private cppu::BaseMutex, public OGLTransitionerImplBase +{ +public: + OGLTransitionerImpl(); + OGLTransitionerImpl(const OGLTransitionerImpl&) = delete; + OGLTransitionerImpl& operator=(const OGLTransitionerImpl&) = delete; + bool setTransition( const std::shared_ptr<OGLTransitionImpl>& pOGLTransition ); + bool initialize( const Reference< presentation::XSlideShowView >& xView, + const Reference< rendering::XBitmap >& xLeavingSlide, + const Reference< rendering::XBitmap >& xEnteringSlide ); + + // XTransition + virtual void SAL_CALL update( double nTime ) override; + virtual void SAL_CALL viewChanged( const Reference< presentation::XSlideShowView >& rView, + const Reference< rendering::XBitmap >& rLeavingBitmap, + const Reference< rendering::XBitmap >& rEnteringBitmap ) override; + +protected: + void disposeTextures(); + + // WeakComponentImplHelperBase + virtual void SAL_CALL disposing() override; + + bool isDisposed() const + { + return (rBHelper.bDisposed || rBHelper.bInDispose); + } + + void createTexture( GLuint* texID, + bool useMipmap, + const uno::Sequence<sal_Int8>& data, + const OGLFormat* pFormat ); + const OGLFormat* chooseFormats(); + +private: + void impl_initializeFlags( bool const bGLXPresent ); + + void impl_dispose(); + + void setSlides( const Reference< rendering::XBitmap >& xLeavingSlide , const uno::Reference< rendering::XBitmap >& xEnteringSlide ); + void impl_prepareSlides(); + + void impl_createTexture( bool useMipmap, const uno::Sequence<sal_Int8>& data, const OGLFormat* pFormat ); + + bool initWindowFromSlideShowView( const uno::Reference< presentation::XSlideShowView >& xView ); + /** After the window has been created, and the slides have been set, we'll initialize the slides with OpenGL. + */ + void GLInitSlides(); + + bool impl_prepareTransition(); + +private: + rtl::Reference<OpenGLContext> mpContext; + + /** OpenGL handle to the leaving slide's texture + */ + GLuint maLeavingSlideGL; + /** OpenGL handle to the entering slide's texture + */ + GLuint maEnteringSlideGL; + + Reference< presentation::XSlideShowView > mxView; + Reference< rendering::XIntegerBitmap > mxLeavingBitmap; + Reference< rendering::XIntegerBitmap > mxEnteringBitmap; + + /** raw bytes of the entering bitmap + */ + uno::Sequence<sal_Int8> maEnteringBytes; + + /** raw bytes of the leaving bitmap + */ + uno::Sequence<sal_Int8> maLeavingBytes; + + bool mbRestoreSync; + + /** the form the raw bytes are in for the bitmaps + */ + rendering::IntegerBitmapLayout maSlideBitmapLayout; + + /** the size of the slides + */ + geometry::IntegerSize2D maSlideSize; + + /** Our Transition to be used. + */ + std::shared_ptr<OGLTransitionImpl> mpTransition; + +public: + /** whether we are running on ATI fglrx with bug related to textures + */ + bool mbBrokenTexturesATI; + + /** GL version + */ + float mnGLVersion; + + /** + Whether the display has GLX extension on X11, always true otherwise (?) + */ + bool mbValidOpenGLContext; + +#if OSL_DEBUG_LEVEL > 0 + std::chrono::steady_clock::time_point m_UpdateStartTime; + std::chrono::steady_clock::time_point m_UpdateEndTime; + std::chrono::steady_clock::time_point m_StartTime; + std::chrono::steady_clock::time_point m_EndTime; + std::chrono::steady_clock::duration m_TotalUpdateDuration; + int mnFrameCount; +#endif +}; + +bool OGLTransitionerImpl::initialize( const Reference< presentation::XSlideShowView >& xView, + const Reference< rendering::XBitmap >& xLeavingSlide, + const Reference< rendering::XBitmap >& xEnteringSlide ) +{ + bool const bValidContext( initWindowFromSlideShowView( xView ) ); + impl_initializeFlags( bValidContext ); + + setSlides( xLeavingSlide, xEnteringSlide ); + + return mbValidOpenGLContext; +} + +void OGLTransitionerImpl::impl_initializeFlags( bool const bValidContext ) +{ + mbValidOpenGLContext = bValidContext; + if ( bValidContext ) { + CHECK_GL_ERROR(); + + mnGLVersion = OpenGLHelper::getGLVersion(); + SAL_INFO("slideshow.opengl", "GL version: " << mnGLVersion << "" ); + +#if defined( UNX ) && !defined( MACOSX ) + const GLubyte* vendor = glGetString( GL_VENDOR ); + /* TODO: check for version once the bug in fglrx driver is fixed */ + mbBrokenTexturesATI = (vendor && strcmp( reinterpret_cast<const char *>(vendor), "ATI Technologies Inc." ) == 0 ); +#endif + + CHECK_GL_ERROR(); + } +} + +bool OGLTransitionerImpl::initWindowFromSlideShowView( const Reference< presentation::XSlideShowView >& xView ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + mxView = xView; + if( !mxView.is() ) + return false; + +#if OSL_DEBUG_LEVEL > 0 + TimerContext aTimerContext("initWindowFromSlideShowView"); +#endif + + /// take the XSlideShowView and extract the parent window from it. see viewmediashape.cxx + uno::Reference< rendering::XCanvas > xCanvas(mxView->getCanvas(), uno::UNO_QUERY_THROW); + uno::Sequence< uno::Any > aDeviceParams; + ::canvas::tools::getDeviceInfo( xCanvas, aDeviceParams ); + + OUString aImplName; + aDeviceParams[ 0 ] >>= aImplName; + + sal_Int64 aVal = 0; + aDeviceParams[1] >>= aVal; + + mpContext = OpenGLContext::Create(); + + OutputDevice* pDevice = reinterpret_cast<OutputDevice*>(aVal); + vcl::Window* pWindow = pDevice ? pDevice->GetOwnerWindow() : nullptr; + + if( !mpContext->init( pWindow) ) { + mpContext->requestLegacyContext(); + if( !mpContext->init( pWindow ) ) + return false; + } + SAL_INFO("slideshow.opengl", "created the context"); + + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + + awt::Rectangle aCanvasArea = mxView->getCanvasArea(); + mpContext->setWinPosAndSize(Point(aCanvasArea.X, aCanvasArea.Y), Size(aCanvasArea.Width, aCanvasArea.Height)); + SAL_INFO("slideshow.opengl", "canvas area: " << aCanvasArea.X << "," << aCanvasArea.Y << " - " << aCanvasArea.Width << "x" << aCanvasArea.Height); + + CHECK_GL_ERROR(); + glEnable(GL_CULL_FACE); + CHECK_GL_ERROR(); + glCullFace(GL_BACK); + CHECK_GL_ERROR(); + glClearColor (0, 0, 0, 0); + CHECK_GL_ERROR(); + glClear(GL_COLOR_BUFFER_BIT); + CHECK_GL_ERROR(); + + mpContext->swapBuffers(); + + CHECK_GL_ERROR(); + + return true; +} + +void OGLTransitionerImpl::setSlides( const uno::Reference< rendering::XBitmap >& xLeavingSlide, + const uno::Reference< rendering::XBitmap >& xEnteringSlide ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + mxLeavingBitmap.set( xLeavingSlide , UNO_QUERY_THROW ); + mxEnteringBitmap.set( xEnteringSlide , UNO_QUERY_THROW ); + + maSlideSize = mxLeavingBitmap->getSize(); + SAL_INFO("slideshow.opengl", "leaving bitmap area: " << maSlideSize.Width << "x" << maSlideSize.Height); + maSlideSize = mxEnteringBitmap->getSize(); + SAL_INFO("slideshow.opengl", "entering bitmap area: " << maSlideSize.Width << "x" << maSlideSize.Height); + + //to avoid annoying flashing under X entering and leaving slides with opengl effects set the leaving + //bitmap as the background pixmap of the opengl child window and the entering bitmap as the background + //pixmap of the non-opengl parent window. If any expose events occur around the start and end of + //the transition then those windows are default filled by X with the desired start/end image so there's + //no visible flash + SystemChildWindow* pChildWindow = mpContext->getChildWindow(); + if (!pChildWindow) + return; + + css::uno::Reference<css::beans::XFastPropertySet> xEnteringFastPropertySet(mxEnteringBitmap, css::uno::UNO_QUERY); + css::uno::Reference<css::beans::XFastPropertySet> xLeavingFastPropertySet(mxLeavingBitmap, css::uno::UNO_QUERY); + css::uno::Sequence<css::uno::Any> aEnteringBitmap; + css::uno::Sequence<css::uno::Any> aLeavingBitmap; + if (xEnteringFastPropertySet && xLeavingFastPropertySet) + { + xEnteringFastPropertySet->getFastPropertyValue(1) >>= aEnteringBitmap; + xLeavingFastPropertySet->getFastPropertyValue(1) >>= aLeavingBitmap; + } + if (aEnteringBitmap.getLength() == 3 && aLeavingBitmap.getLength() == 3) + pChildWindow->SetLeaveEnterBackgrounds(aLeavingBitmap, aEnteringBitmap); +} + + +void OGLTransitionerImpl::impl_prepareSlides() +{ + geometry::IntegerRectangle2D aSlideRect; + aSlideRect.X1 = 0; + aSlideRect.X2 = maSlideSize.Width; + aSlideRect.Y1 = 0; + aSlideRect.Y2 = maSlideSize.Height; + + CHECK_GL_ERROR(); + mpContext->sync(); + CHECK_GL_ERROR(); + + maLeavingBytes = mxLeavingBitmap->getData(maSlideBitmapLayout, aSlideRect); + maEnteringBytes = mxEnteringBitmap->getData(maSlideBitmapLayout, aSlideRect); + + CHECK_GL_ERROR(); + GLInitSlides(); + + SAL_WARN_IF(maSlideBitmapLayout.PlaneStride != 0, "slideshow.opengl","only handle no plane stride now"); + + mpContext->sync(); + + CHECK_GL_ERROR(); + + // synchronized X still gives us much smoother play + // I suspect some issues in above code in slideshow + // synchronize whole transition for now + const GLWindow& rGLWindow(mpContext->getOpenGLWindow()); + mbRestoreSync = rGLWindow.Synchronize(true); +} + +bool OGLTransitionerImpl::impl_prepareTransition() +{ + if( mpTransition && mpTransition->getSettings().mnRequiredGLVersion <= mnGLVersion ) + return mpTransition->prepare( maLeavingSlideGL, maEnteringSlideGL, mpContext.get() ); + return false; +} + +bool OGLTransitionerImpl::setTransition( const std::shared_ptr<OGLTransitionImpl>& pTransition ) +{ + if ( mpTransition ) // already initialized + return true; + + mpTransition = pTransition; + + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + + bool succeeded = impl_prepareTransition(); + if (!succeeded) { + mpTransition = nullptr; + return false; + } + + impl_prepareSlides(); + + // tdf#91456: When the OpenGL context is initialized but nothing has been rendered on it + // it can happen, that an "empty" screen is drawn. Therefore, drawing the content of time 0 + // onto the context + update(0); + + return true; +} + +void OGLTransitionerImpl::createTexture( GLuint* texID, + bool useMipmap, + const uno::Sequence<sal_Int8>& data, + const OGLFormat* pFormat ) +{ + CHECK_GL_ERROR(); + glDeleteTextures( 1, texID ); + glGenTextures( 1, texID ); + glBindTexture( GL_TEXTURE_2D, *texID ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + CHECK_GL_ERROR(); + + impl_createTexture( useMipmap, data, pFormat ); + + SAL_WARN_IF(!glIsTexture(*texID), "slideshow.opengl", "Can't generate Leaving slide textures in OpenGL"); + CHECK_GL_ERROR(); +} + +class OGLColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace > +{ +private: + uno::Sequence< sal_Int8 > maComponentTags; + uno::Sequence< sal_Int32 > maBitCounts; + + virtual sal_Int8 SAL_CALL getType( ) override + { + return rendering::ColorSpaceType::RGB; + } + virtual uno::Sequence< sal_Int8 > SAL_CALL getComponentTags( ) override + { + return maComponentTags; + } + virtual sal_Int8 SAL_CALL getRenderingIntent( ) override + { + return rendering::RenderingIntent::PERCEPTUAL; + } + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override + { + return uno::Sequence< beans::PropertyValue >(); + } + virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( const rendering::RGBColor& rIn : rgbColor ) + { + *pColors++ = rIn.Red; + *pColors++ = rIn.Green; + *pColors++ = rIn.Blue; + *pColors++ = 1.0; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( const rendering::ARGBColor& rIn : rgbColor ) + { + *pColors++ = rIn.Red; + *pColors++ = rIn.Green; + *pColors++ = rIn.Blue; + *pColors++ = rIn.Alpha; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( const rendering::ARGBColor& rIn : rgbColor ) + { + *pColors++ = rIn.Red/rIn.Alpha; + *pColors++ = rIn.Green/rIn.Alpha; + *pColors++ = rIn.Blue/rIn.Alpha; + *pColors++ = rIn.Alpha; + } + return aRes; + } + + // XIntegerBitmapColorSpace + virtual sal_Int32 SAL_CALL getBitsPerPixel( ) override + { + return 32; + } + virtual uno::Sequence< sal_Int32 > SAL_CALL getComponentBitCounts( ) override + { + return maBitCounts; + } + virtual sal_Int8 SAL_CALL getEndianness( ) override + { + return util::Endianness::LITTLE; + } + virtual uno::Sequence<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<OGLColorSpace*>(targetColorSpace.get()) ) + { + const sal_Int32 nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + std::transform(deviceColor.begin(), deviceColor.end(), aRes.getArray(), + vcl::unotools::toDoubleColor); + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + } + virtual uno::Sequence< sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< sal_Int8 >& deviceColor, + const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<OGLColorSpace*>(targetColorSpace.get()) ) + { + // it's us, so simply pass-through the data + return deviceColor; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertIntegerFromARGB(aIntermediate); + } + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor( + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(pIn[3]), + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const sal_Int8 nAlpha( pIn[3] ); + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(nAlpha), + vcl::unotools::toDoubleColor(nAlpha*pIn[0]), + vcl::unotools::toDoubleColor(nAlpha*pIn[1]), + vcl::unotools::toDoubleColor(nAlpha*pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< sal_Int8 > SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( const rendering::RGBColor& rIn : rgbColor ) + { + *pColors++ = vcl::unotools::toByteColor(rIn.Red); + *pColors++ = vcl::unotools::toByteColor(rIn.Green); + *pColors++ = vcl::unotools::toByteColor(rIn.Blue); + *pColors++ = -1; + } + return aRes; + } + + virtual uno::Sequence< sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( const rendering::ARGBColor& rIn : rgbColor ) + { + *pColors++ = vcl::unotools::toByteColor(rIn.Red); + *pColors++ = vcl::unotools::toByteColor(rIn.Green); + *pColors++ = vcl::unotools::toByteColor(rIn.Blue); + *pColors++ = vcl::unotools::toByteColor(rIn.Alpha); + } + return aRes; + } + + virtual uno::Sequence< sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const sal_Int32 nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( const rendering::ARGBColor& rIn : rgbColor ) + { + *pColors++ = vcl::unotools::toByteColor(rIn.Red/rIn.Alpha); + *pColors++ = vcl::unotools::toByteColor(rIn.Green/rIn.Alpha); + *pColors++ = vcl::unotools::toByteColor(rIn.Blue/rIn.Alpha); + *pColors++ = vcl::unotools::toByteColor(rIn.Alpha); + } + return aRes; + } + +public: + OGLColorSpace() : + maComponentTags(4), + maBitCounts(4) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_RED; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_BLUE; + pTags[3] = rendering::ColorComponentTag::ALPHA; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = + pBitCounts[3] = 8; + } +}; + +struct OGLColorSpaceHolder : public rtl::StaticWithInit<uno::Reference<rendering::XIntegerBitmapColorSpace>, OGLColorSpaceHolder> +{ + uno::Reference<rendering::XIntegerBitmapColorSpace> operator()() + { + return new OGLColorSpace(); + } +}; + +uno::Reference<rendering::XIntegerBitmapColorSpace> const & +getOGLColorSpace() +{ + return OGLColorSpaceHolder::get(); +} + +void buildMipmaps( + GLint internalFormat, GLsizei width, GLsizei height, GLenum format, + GLenum type, const void * data) +{ + if (epoxy_has_gl_extension("GL_ARB_framebuffer_object")) { + glTexImage2D( + GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, + data); + glGenerateMipmap(GL_TEXTURE_2D); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); + glTexImage2D( + GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, + data); + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); +} + +void OGLTransitionerImpl::impl_createTexture( + bool useMipmap, + const uno::Sequence<sal_Int8>& data, + const OGLFormat* pFormat ) +{ + if( !pFormat ) + { + CHECK_GL_ERROR(); + // force-convert color to ARGB8888 int color space + uno::Sequence<sal_Int8> tempBytes( + maSlideBitmapLayout.ColorSpace->convertToIntegerColorSpace( + data, + getOGLColorSpace())); + buildMipmaps( GL_RGBA, + maSlideSize.Width, + maSlideSize.Height, + GL_RGBA, + GL_UNSIGNED_BYTE, + &tempBytes[0]); + + if (epoxy_has_gl_extension("GL_EXT_texture_filter_anisotropic")) + { + //anistropic filtering (to make texturing not suck when looking at polygons from oblique angles) + GLfloat largest_supported_anisotropy; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_supported_anisotropy); + } + } else { + if( mpTransition && !mbBrokenTexturesATI && !useMipmap) { + glTexImage2D( GL_TEXTURE_2D, 0, pFormat->nInternalFormat, maSlideSize.Width, maSlideSize.Height, 0, pFormat->eFormat, pFormat->eType, &data[0] ); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + } else { + buildMipmaps( pFormat->nInternalFormat, maSlideSize.Width, maSlideSize.Height, pFormat->eFormat, pFormat->eType, &data[0] ); + + if (epoxy_has_gl_extension("GL_EXT_texture_filter_anisotropic")) + { + //anistropic filtering (to make texturing not suck when looking at polygons from oblique angles) + GLfloat largest_supported_anisotropy; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_supported_anisotropy ); + } + } + } + CHECK_GL_ERROR(); +} + +const OGLFormat* OGLTransitionerImpl::chooseFormats() +{ + const OGLFormat* pDetectedFormat=nullptr; + uno::Reference<rendering::XIntegerBitmapColorSpace> xIntColorSpace( + maSlideBitmapLayout.ColorSpace); + + if( xIntColorSpace->getType() == rendering::ColorSpaceType::RGB || + xIntColorSpace->getType() == rendering::ColorSpaceType::SRGB ) + { + /* table for canvas->OGL format mapping. outer index is number + of color components (0:3, 1:4), then comes bits per pixel + (0:16, 1:24, 2:32), then channel ordering: (0:rgba, 1:bgra, + 2:argb, 3:abgr) + */ + static const OGLFormat lcl_RGB24[] = + { + // 24 bit RGB + {3, GL_BGR, GL_UNSIGNED_BYTE}, + {3, GL_RGB, GL_UNSIGNED_BYTE}, + {3, GL_BGR, GL_UNSIGNED_BYTE}, + {3, GL_RGB, GL_UNSIGNED_BYTE} + }; + +#if defined(GL_VERSION_1_2) && defined(GLU_VERSION_1_3) + // more format constants available + static const OGLFormat lcl_RGB16[] = + { + // 16 bit RGB + {3, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, + {3, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, + {3, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, + {3, GL_RGB, GL_UNSIGNED_SHORT_5_6_5} + }; + + static const OGLFormat lcl_ARGB16_4[] = + { + // 16 bit ARGB + {4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, + {4, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, + {4, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4}, + {4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4} + }; + + static const OGLFormat lcl_ARGB16_5[] = + { + // 16 bit ARGB + {4, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, + {4, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, + {4, GL_BGRA, GL_UNSIGNED_SHORT_5_5_5_1}, + {4, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1} + }; + + static const OGLFormat lcl_ARGB32[] = + { + // 32 bit ARGB + {4, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, + {4, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}, + {4, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8}, + {4, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8} + }; + + const uno::Sequence<sal_Int8> aComponentTags( + xIntColorSpace->getComponentTags()); + const uno::Sequence<sal_Int32> aComponentBitcounts( + xIntColorSpace->getComponentBitCounts()); + const sal_Int32 nNumComponents( aComponentBitcounts.getLength() ); + const sal_Int32 nBitsPerPixel( xIntColorSpace->getBitsPerPixel() ); + + // supported component ordering? + const int nComponentOrderIndex( + calcComponentOrderIndex(aComponentTags)); + if( nComponentOrderIndex != -1 ) + { + switch( nBitsPerPixel ) + { + case 16: + if( nNumComponents == 3 ) + { + pDetectedFormat = &lcl_RGB16[nComponentOrderIndex]; + } + else if( nNumComponents == 4 ) + { + if( aComponentBitcounts[1] == 4 ) + { + pDetectedFormat = &lcl_ARGB16_4[nComponentOrderIndex]; + } + else if( aComponentBitcounts[1] == 5 ) + { + pDetectedFormat = &lcl_ARGB16_5[nComponentOrderIndex]; + } + } + break; + case 24: + if( nNumComponents == 3 ) + { + pDetectedFormat = &lcl_RGB24[nComponentOrderIndex]; + } + break; + case 32: + if ( nNumComponents == 4 ) + { + pDetectedFormat = &lcl_ARGB32[nComponentOrderIndex]; + } + break; + } + } +#else + const uno::Sequence<sal_Int8> aComponentTags( + xIntColorSpace->getComponentTags()); + const int nComponentOrderIndex(calcComponentOrderIndex(aComponentTags)); + if( aComponentTags.getLength() == 3 && + nComponentOrderIndex != -1 && + xIntColorSpace->getBitsPerPixel() == 24 ) + { + pDetectedFormat = &lcl_RGB24[nComponentOrderIndex]; + } +#endif + } + + return pDetectedFormat; +} + +void OGLTransitionerImpl::GLInitSlides() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed() || !mpTransition || mpTransition->getSettings().mnRequiredGLVersion > mnGLVersion) + return; + +#if OSL_DEBUG_LEVEL > 0 + TimerContext aTimerContext("texture creation"); +#endif + + mpContext->makeCurrent(); + + const OGLFormat* pFormat = chooseFormats(); + + CHECK_GL_ERROR(); + createTexture( &maLeavingSlideGL, + mpTransition->getSettings().mbUseMipMapLeaving, + maLeavingBytes, + pFormat ); + + createTexture( &maEnteringSlideGL, + mpTransition->getSettings().mbUseMipMapEntering, + maEnteringBytes, + pFormat ); + + CHECK_GL_ERROR(); + mpContext->sync(); + CHECK_GL_ERROR(); +} + +void SAL_CALL OGLTransitionerImpl::update( double nTime ) +{ +#if OSL_DEBUG_LEVEL > 0 + mnFrameCount ++; + m_UpdateStartTime = std::chrono::steady_clock::now(); + if( mnFrameCount == 1 ) { + m_StartTime = m_UpdateStartTime; + m_TotalUpdateDuration = std::chrono::seconds(0); + } +#endif + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed() || !mbValidOpenGLContext || !mpTransition || mpTransition->getSettings().mnRequiredGLVersion > mnGLVersion) + return; + + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + + glEnable(GL_DEPTH_TEST); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + CHECK_GL_ERROR(); + + const GLWindow& rGLWindow(mpContext->getOpenGLWindow()); + mpTransition->display(nTime, maLeavingSlideGL, maEnteringSlideGL, + maSlideSize.Width, maSlideSize.Height, + static_cast<double>(rGLWindow.Width), + static_cast<double>(rGLWindow.Height), + mpContext.get()); + + mpContext->swapBuffers(); + + mpContext->show(); + mpContext->sync(); + CHECK_GL_ERROR(); + +#if OSL_DEBUG_LEVEL > 0 + m_UpdateEndTime = std::chrono::steady_clock::now(); + + SAL_INFO("slideshow.opengl", "update time: " << nTime); + SAL_INFO("slideshow.opengl", "update took: " << std::chrono::duration_cast<std::chrono::milliseconds>(m_UpdateEndTime - m_UpdateStartTime).count()); + m_TotalUpdateDuration += m_UpdateEndTime - m_UpdateStartTime; +#endif +} + +void SAL_CALL OGLTransitionerImpl::viewChanged( const Reference< presentation::XSlideShowView >& rView, + const Reference< rendering::XBitmap >& rLeavingBitmap, + const Reference< rendering::XBitmap >& rEnteringBitmap ) +{ + SAL_INFO("slideshow.opengl", "transitioner: view changed"); + + impl_dispose(); + + initWindowFromSlideShowView( rView ); + setSlides( rLeavingBitmap, rEnteringBitmap ); + impl_prepareSlides(); + impl_prepareTransition(); +} + +void OGLTransitionerImpl::disposeTextures() +{ + if (!mbValidOpenGLContext) + return; + + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + + glDeleteTextures(1,&maLeavingSlideGL); + maLeavingSlideGL = 0; + glDeleteTextures(1,&maEnteringSlideGL); + maEnteringSlideGL = 0; + + CHECK_GL_ERROR(); +} + +void OGLTransitionerImpl::impl_dispose() +{ + if (mbValidOpenGLContext) + { + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + } + + if( mpTransition && mpTransition->getSettings().mnRequiredGLVersion <= mnGLVersion ) + mpTransition->finish(); + disposeTextures(); + if( mpContext.is() ) + mpContext->dispose(); + mpContext.clear(); +} + +// we are about to be disposed (someone call dispose() on us) +void OGLTransitionerImpl::disposing() +{ + osl::MutexGuard const guard( m_aMutex ); + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("slideshow.opengl", "dispose " << this); + if( mnFrameCount ) { + m_EndTime = std::chrono::steady_clock::now(); + auto const duration = m_EndTime - m_StartTime; + SAL_INFO("slideshow.opengl", + "whole transition (frames: " << mnFrameCount + << ") took: " << std::chrono::duration_cast<std::chrono::microseconds>(duration).count() + << " fps: " + << ((static_cast<double>(mnFrameCount)*1000000000.0)/std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()) + << " time spent in updates: " << std::chrono::duration_cast<std::chrono::microseconds>(m_TotalUpdateDuration).count() + << " percentage of transition time: " + << (100*((static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(m_TotalUpdateDuration).count()))/(static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count())))) + << '%' + ); + } +#endif + + if (mbRestoreSync && mpContext.is()) { + // try to reestablish synchronize state + const char* sal_synchronize = getenv("SAL_SYNCHRONIZE"); + mpContext->getOpenGLWindow().Synchronize(sal_synchronize && *sal_synchronize == '1' ); + } + + impl_dispose(); + + mpTransition.reset(); + + mxLeavingBitmap.clear(); + mxEnteringBitmap.clear(); + mxView.clear(); +} + +OGLTransitionerImpl::OGLTransitionerImpl() + : OGLTransitionerImplBase(m_aMutex) + , mpContext() + , maLeavingSlideGL(0) + , maEnteringSlideGL(0) + , mxView() + , maEnteringBytes() + , maLeavingBytes() + , mbRestoreSync(false) + , maSlideBitmapLayout() + , maSlideSize() + , mbBrokenTexturesATI(false) + , mnGLVersion(0) + , mbValidOpenGLContext(false) +#if OSL_DEBUG_LEVEL > 0 + , mnFrameCount(0) +#endif +{ +} + +typedef cppu::WeakComponentImplHelper<presentation::XTransitionFactory, lang::XServiceInfo> OGLTransitionFactoryImplBase; + +class OGLTransitionFactoryImpl : private cppu::BaseMutex, public OGLTransitionFactoryImplBase +{ +public: + explicit OGLTransitionFactoryImpl() : + OGLTransitionFactoryImplBase(m_aMutex) + {} + + // XServiceInfo + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override + { + return { "com.sun.star.presentation.TransitionFactory" }; + } + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.presentation.OGLTransitionFactory"; + } + virtual sal_Bool SAL_CALL supportsService(const OUString& aServiceName) override + { + return cppu::supportsService(this, aServiceName); + } + + // XTransitionFactory + virtual sal_Bool SAL_CALL hasTransition( sal_Int16 transitionType, sal_Int16 transitionSubType ) override + { + if( !OpenGLHelper::supportsOpenGL()) + return false; + // A set of css::animation::TransitionSubType that don't have any meaning (in the SMIL 2.0 + // standard) for MISCSHAPEWIPE have been chosen to refer to some of these "fancy" optional + // transitions. (The only subtypes of 'miscShapeWipe' defined in the standard are 'heart' + // and 'keyhole'.) The set of subtypes used seems to be a bit random; it starts from the + // beginning of the list (in the order (numeric) in our TransitionSubType set of constants) + // but then jumps a bit randomly. The numeric values as such have no meaning, but still. + + if( transitionType == animations::TransitionType::MISCSHAPEWIPE ) { + switch( transitionSubType ) + { + case animations::TransitionSubType::LEFTTORIGHT: // 1 + case animations::TransitionSubType::TOPTOBOTTOM: // 2 + case animations::TransitionSubType::TOPLEFT: // 3 + case animations::TransitionSubType::TOPRIGHT: // 4 + case animations::TransitionSubType::BOTTOMRIGHT: // 5 + case animations::TransitionSubType::BOTTOMLEFT: // 6 + case animations::TransitionSubType::TOPCENTER: // 7 + case animations::TransitionSubType::RIGHTCENTER: // 8 + case animations::TransitionSubType::BOTTOMCENTER: // 9 + case animations::TransitionSubType::CORNERSIN: // 11 + case animations::TransitionSubType::CORNERSOUT: // 12 + case animations::TransitionSubType::VERTICAL: // 13 + case animations::TransitionSubType::HORIZONTAL: // 14 + case animations::TransitionSubType::DIAMOND: // 26 + case animations::TransitionSubType::CIRCLE: // 27 + case animations::TransitionSubType::HEART: // 31 + case animations::TransitionSubType::FANOUTHORIZONTAL: // 55 + case animations::TransitionSubType::ACROSS: // 108 + return true; + + default: + return false; + } + } else if( transitionType == animations::TransitionType::FADE && transitionSubType == animations::TransitionSubType::CROSSFADE ) { + return true; + } else if( transitionType == animations::TransitionType::FADE && transitionSubType == animations::TransitionSubType::FADEOVERCOLOR ) { + return true; + } else if( transitionType == animations::TransitionType::IRISWIPE && transitionSubType == animations::TransitionSubType::DIAMOND ) { + return true; + } else if( transitionType == animations::TransitionType::ZOOM && transitionSubType == animations::TransitionSubType::ROTATEIN ) { + return true; + } else + return false; + } + + virtual uno::Reference< presentation::XTransition > SAL_CALL createTransition( + sal_Int16 transitionType, + sal_Int16 transitionSubType, + sal_Int32 transitionFadeColor, + const uno::Reference< presentation::XSlideShowView >& view, + const uno::Reference< rendering::XBitmap >& leavingBitmap, + const uno::Reference< rendering::XBitmap >& enteringBitmap ) override + { + if( !hasTransition( transitionType, transitionSubType ) ) + return uno::Reference< presentation::XTransition >(); + + rtl::Reference< OGLTransitionerImpl > xRes( new OGLTransitionerImpl() ); + if ( !xRes->initialize( view, leavingBitmap, enteringBitmap ) ) + return uno::Reference< presentation::XTransition >(); + + std::shared_ptr<OGLTransitionImpl> pTransition; + + if( transitionType == animations::TransitionType::MISCSHAPEWIPE ) { + switch( transitionSubType ) + { + case animations::TransitionSubType::LEFTTORIGHT: + pTransition = makeFallLeaving(); + break; + case animations::TransitionSubType::TOPTOBOTTOM: + pTransition = makeTurnAround(); + break; + case animations::TransitionSubType::TOPLEFT: + pTransition = makeIris(); + break; + case animations::TransitionSubType::TOPRIGHT: + pTransition = makeTurnDown(); + break; + case animations::TransitionSubType::BOTTOMRIGHT: + pTransition = makeRochade(); + break; + case animations::TransitionSubType::BOTTOMLEFT: + pTransition = makeVenetianBlinds( true, 8 ); + break; + case animations::TransitionSubType::TOPCENTER: + pTransition = makeVenetianBlinds( false, 6 ); + break; + case animations::TransitionSubType::RIGHTCENTER: + pTransition = makeStatic(); + break; + case animations::TransitionSubType::BOTTOMCENTER: + pTransition = makeDissolve(); + break; + case animations::TransitionSubType::CORNERSIN: + pTransition = makeInsideCubeFaceToLeft(); + break; + case animations::TransitionSubType::CORNERSOUT: + pTransition = makeOutsideCubeFaceToLeft(); + break; + case animations::TransitionSubType::VERTICAL: + pTransition = makeVortex(); + break; + case animations::TransitionSubType::HORIZONTAL: + pTransition = makeRipple(); + break; + case animations::TransitionSubType::CIRCLE: + pTransition = makeRevolvingCircles(8,128); + break; + case animations::TransitionSubType::FANOUTHORIZONTAL: + pTransition = makeHelix(20); + break; + case animations::TransitionSubType::ACROSS: + pTransition = makeNByMTileFlip(8,6); + break; + case animations::TransitionSubType::DIAMOND: + pTransition = makeGlitter(); + break; + case animations::TransitionSubType::HEART: + pTransition = makeHoneycomb(); + break; + } + } else if( transitionType == animations::TransitionType::FADE && transitionSubType == animations::TransitionSubType::CROSSFADE ) { + pTransition = makeFadeSmoothly(); + } else if( transitionType == animations::TransitionType::FADE && transitionSubType == animations::TransitionSubType::FADEOVERCOLOR ) { + pTransition = makeFadeThroughColor( transitionFadeColor == 0xffffff ); + } else if( transitionType == animations::TransitionType::IRISWIPE && transitionSubType == animations::TransitionSubType::DIAMOND ) { + pTransition = makeDiamond(); + } else if( transitionType == animations::TransitionType::ZOOM && transitionSubType == animations::TransitionSubType::ROTATEIN ) { + pTransition = makeNewsflash(); + } + + if ( !pTransition || !xRes->setTransition(pTransition) ) + return uno::Reference< presentation::XTransition >(); + + return xRes; + } +}; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +slideshow_OGLTransitionFactoryImpl_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new OGLTransitionFactoryImpl()); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/opengl/ogltrans.component b/slideshow/source/engine/opengl/ogltrans.component new file mode 100644 index 000000000..5ce59a80e --- /dev/null +++ b/slideshow/source/engine/opengl/ogltrans.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.presentation.OGLTransitionFactory" + constructor="slideshow_OGLTransitionFactoryImpl_get_implementation"> + <service name="com.sun.star.presentation.TransitionFactory"/> + </implementation> +</component> diff --git a/slideshow/source/engine/pointersymbol.cxx b/slideshow/source/engine/pointersymbol.cxx new file mode 100644 index 000000000..4bc230154 --- /dev/null +++ b/slideshow/source/engine/pointersymbol.cxx @@ -0,0 +1,197 @@ +/* -*- 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 <canvas/canvastools.hxx> + +#include <cppcanvas/customsprite.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <tools/diagnose_ex.h> + +#include "pointersymbol.hxx" +#include <eventmultiplexer.hxx> + +#include <algorithm> + + +using namespace com::sun::star; + +namespace slideshow::internal { + +PointerSymbolSharedPtr PointerSymbol::create( const uno::Reference<rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const UnoViewContainer& rViewContainer ) +{ + PointerSymbolSharedPtr pRet( + new PointerSymbol( xBitmap, + rScreenUpdater, + rViewContainer )); + + rEventMultiplexer.addViewHandler( pRet ); + + return pRet; +} + +PointerSymbol::PointerSymbol( uno::Reference<rendering::XBitmap> const & xBitmap, + ScreenUpdater& rScreenUpdater, + const UnoViewContainer& rViewContainer ) : + mxBitmap(xBitmap), + maViews(), + mrScreenUpdater( rScreenUpdater ), + maPos(), + mbVisible(false) +{ + for( const auto& rView : rViewContainer ) + viewAdded( rView ); +} + +void PointerSymbol::setVisible( const bool bVisible ) +{ + if( mbVisible == bVisible ) + return; + + mbVisible = bVisible; + + for( const auto& rView : maViews ) + { + if( rView.second ) + { + if( bVisible ) + rView.second->show(); + else + rView.second->hide(); + } + } + + // sprites changed, need a screen update for this frame. + mrScreenUpdater.requestImmediateUpdate(); +} + +basegfx::B2DPoint PointerSymbol::calcSpritePos(UnoViewSharedPtr const & rView) const +{ + const awt::Rectangle aViewArea( rView->getUnoView()->getCanvasArea() ); + const geometry::IntegerSize2D realTranslationOffset ( rView->getTranslationOffset() ); + + return basegfx::B2DPoint( + realTranslationOffset.Width + ((aViewArea.Width - aViewArea.X) - 2 * realTranslationOffset.Width) * maPos.X, + realTranslationOffset.Height + ((aViewArea.Height - aViewArea.Y) - 2 * realTranslationOffset.Height) * maPos.Y); +} + +void PointerSymbol::viewAdded( const UnoViewSharedPtr& rView ) +{ + cppcanvas::CustomSpriteSharedPtr sprite; + + try + { + const geometry::IntegerSize2D spriteSize( mxBitmap->getSize() ); + sprite = rView->createSprite( basegfx::B2DVector( spriteSize.Width, + spriteSize.Height ), + 1000.0 ); // sprite should be in front of all + // other sprites + rendering::ViewState viewState; + canvas::tools::initViewState( viewState ); + rendering::RenderState renderState; + canvas::tools::initRenderState( renderState ); + sprite->getContentCanvas()->getUNOCanvas()->drawBitmap( + mxBitmap, viewState, renderState ); + + sprite->setAlpha( 0.9 ); + sprite->movePixel( calcSpritePos( rView ) ); + if( mbVisible ) + sprite->show(); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + + maViews.emplace_back( rView, sprite ); +} + +void PointerSymbol::viewRemoved( const UnoViewSharedPtr& rView ) +{ + maViews.erase( + std::remove_if( + maViews.begin(), maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ), + maViews.end() ); +} + +void PointerSymbol::viewChanged( const UnoViewSharedPtr& rView ) +{ + // find entry corresponding to modified view + ViewsVecT::iterator aModifiedEntry( + std::find_if( + maViews.begin(), + maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ) ); + + OSL_ASSERT( aModifiedEntry != maViews.end() ); + if( aModifiedEntry == maViews.end() ) + return; + + if( aModifiedEntry->second ) + aModifiedEntry->second->movePixel( + calcSpritePos(aModifiedEntry->first) ); +} + +void PointerSymbol::viewsChanged() +{ + // reposition sprites on all views + for( const auto& rView : maViews ) + { + if( rView.second ) + rView.second->movePixel( + calcSpritePos( rView.first ) ); + } +} + +void PointerSymbol::viewsChanged(const geometry::RealPoint2D pos) +{ + if( pos.X == maPos.X && pos.Y == maPos.Y ) + return; + + maPos = pos; + + // reposition sprites on all views + for( const auto& rView : maViews ) + { + if( rView.second ) + { + rView.second->movePixel( + calcSpritePos( rView.first ) ); + mrScreenUpdater.notifyUpdate(); + mrScreenUpdater.commitUpdates(); + } + } +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/pointersymbol.hxx b/slideshow/source/engine/pointersymbol.hxx new file mode 100644 index 000000000..b28ddcf09 --- /dev/null +++ b/slideshow/source/engine/pointersymbol.hxx @@ -0,0 +1,77 @@ +/* -*- 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/. + * + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_POINTERSYMBOL_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_POINTERSYMBOL_HXX + +#include <com/sun/star/rendering/XBitmap.hpp> +#include <cppcanvas/sprite.hxx> + +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <vieweventhandler.hxx> +#include <screenupdater.hxx> +#include <eventmultiplexer.hxx> +#include <unoview.hxx> + +#include <vector> + +using namespace com::sun::star; + +namespace slideshow::internal { + +class EventMultiplexer; +typedef std::shared_ptr<class PointerSymbol> PointerSymbolSharedPtr; + +/// On-screen 'laser pointer' from the Impress remote control +class PointerSymbol : public ViewEventHandler +{ +public: + static PointerSymbolSharedPtr create( const css::uno::Reference<css::rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const UnoViewContainer& rViewContainer ); + + /** Use this method to update the pointer's position + */ + void setVisible( const bool bVisible ); + void viewsChanged(const css::geometry::RealPoint2D pos); + +private: + PointerSymbol( const css::uno::Reference<css::rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + const UnoViewContainer& rViewContainer ); + + // ViewEventHandler + virtual void viewAdded( const UnoViewSharedPtr& rView ) override; + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override; + virtual void viewChanged( const UnoViewSharedPtr& rView ) override; + virtual void viewsChanged() override; + + ::basegfx::B2DPoint calcSpritePos( UnoViewSharedPtr const & rView ) const; + + typedef ::std::vector< + ::std::pair<UnoViewSharedPtr, + cppcanvas::CustomSpriteSharedPtr> > ViewsVecT; + + css::uno::Reference<css::rendering::XBitmap> mxBitmap; + + ViewsVecT maViews; + ScreenUpdater& mrScreenUpdater; + css::geometry::RealPoint2D maPos; + bool mbVisible; +}; + +} // namespace presentation::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/rehearsetimingsactivity.cxx b/slideshow/source/engine/rehearsetimingsactivity.cxx new file mode 100644 index 000000000..dd4e5beac --- /dev/null +++ b/slideshow/source/engine/rehearsetimingsactivity.cxx @@ -0,0 +1,546 @@ +/* -*- 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 <rtl/ustrbuf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include <vcl/settings.hxx> + +#include <cppcanvas/vclfactory.hxx> +#include <basegfx/range/b2drange.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <eventqueue.hxx> +#include <screenupdater.hxx> +#include <eventmultiplexer.hxx> +#include <activitiesqueue.hxx> +#include <slideshowcontext.hxx> +#include <mouseeventhandler.hxx> +#include "rehearsetimingsactivity.hxx" + +#include <algorithm> + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +namespace slideshow::internal { + +class RehearseTimingsActivity::WakeupEvent : public Event +{ +public: + WakeupEvent( std::shared_ptr< ::canvas::tools::ElapsedTime > const& pTimeBase, + ActivitySharedPtr const& rActivity, + ActivitiesQueue & rActivityQueue ) : + Event("WakeupEvent"), + maTimer(pTimeBase), + mnNextTime(0.0), + mpActivity(rActivity), + mrActivityQueue( rActivityQueue ) + {} + + WakeupEvent( const WakeupEvent& ) = delete; + WakeupEvent& operator=( const WakeupEvent& ) = delete; + + virtual void dispose() override {} + virtual bool fire() override + { + ActivitySharedPtr pActivity( mpActivity.lock() ); + if( !pActivity ) + return false; + + return mrActivityQueue.addActivity( pActivity ); + } + + virtual bool isCharged() const override { return true; } + virtual double getActivationTime( double nCurrentTime ) const override + { + const double nElapsedTime( maTimer.getElapsedTime() ); + + return ::std::max( nCurrentTime, + nCurrentTime - nElapsedTime + mnNextTime ); + } + + /// Start the internal timer + void start() { maTimer.reset(); } + + /** Set the next timeout this object should generate. + + @param nextTime + Absolute time, measured from the last start() call, + when this event should wakeup the Activity again. If + your time is relative, simply call start() just before + every setNextTimeout() call. + */ + void setNextTimeout( double nextTime ) { mnNextTime = nextTime; } + +private: + ::canvas::tools::ElapsedTime maTimer; + double mnNextTime; + std::weak_ptr<Activity> mpActivity; + ActivitiesQueue& mrActivityQueue; +}; + +class RehearseTimingsActivity::MouseHandler : public MouseEventHandler +{ +public: + explicit MouseHandler( RehearseTimingsActivity& rta ); + + MouseHandler( const MouseHandler& ) = delete; + MouseHandler& operator=( const MouseHandler& ) = delete; + + void reset(); + bool hasBeenClicked() const { return mbHasBeenClicked; } + + // MouseEventHandler + virtual bool handleMousePressed( awt::MouseEvent const & evt ) override; + virtual bool handleMouseReleased( awt::MouseEvent const & evt ) override; + virtual bool handleMouseDragged( awt::MouseEvent const & evt ) override; + virtual bool handleMouseMoved( awt::MouseEvent const & evt ) override; + +private: + bool isInArea( css::awt::MouseEvent const & evt ) const; + void updatePressedState( const bool pressedState ) const; + + RehearseTimingsActivity& mrActivity; + bool mbHasBeenClicked; + bool mbMouseStartedInArea; +}; + +const sal_Int32 LEFT_BORDER_SPACE = 10; +const sal_Int32 LOWER_BORDER_SPACE = 30; + +RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rContext ) : + mrEventQueue(rContext.mrEventQueue), + mrScreenUpdater(rContext.mrScreenUpdater), + mrEventMultiplexer(rContext.mrEventMultiplexer), + mrActivitiesQueue(rContext.mrActivitiesQueue), + maElapsedTime( rContext.mrEventQueue.getTimer() ), + maViews(), + maSpriteRectangle(), + maFont( Application::GetSettings().GetStyleSettings().GetLabelFont() ), + mpWakeUpEvent(), + mpMouseHandler(), + maSpriteSizePixel(), + mnYOffset(0), + mbActive(false), + mbDrawPressed(false) +{ + maFont.SetFontHeight( maFont.GetFontHeight() * 2 ); + maFont.SetAverageFontWidth( maFont.GetAverageFontWidth() * 2 ); + maFont.SetAlignment( ALIGN_BASELINE ); + maFont.SetColor( COL_BLACK ); + + // determine sprite size (in pixel): + ScopedVclPtrInstance< VirtualDevice > blackHole; + blackHole->EnableOutput(false); + blackHole->SetFont( maFont ); + blackHole->SetMapMode(MapMode(MapUnit::MapPixel)); + tools::Rectangle rect; + const FontMetric metric( blackHole->GetFontMetric() ); + blackHole->GetTextBoundRect( rect, "XX:XX:XX" ); + maSpriteSizePixel.setX( rect.getWidth() * 12 / 10 ); + maSpriteSizePixel.setY( metric.GetLineHeight() * 11 / 10 ); + mnYOffset = (metric.GetAscent() + (metric.GetLineHeight() / 20)); + + for( const auto& rView : rContext.mrViewContainer ) + viewAdded( rView ); +} + +RehearseTimingsActivity::~RehearseTimingsActivity() +{ + try + { + stop(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } +} + +std::shared_ptr<RehearseTimingsActivity> RehearseTimingsActivity::create( + const SlideShowContext& rContext ) +{ + std::shared_ptr<RehearseTimingsActivity> pActivity( + new RehearseTimingsActivity( rContext )); + + pActivity->mpMouseHandler = + std::make_shared<MouseHandler>(*pActivity); + pActivity->mpWakeUpEvent = + std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(), + pActivity, + rContext.mrActivitiesQueue ); + + rContext.mrEventMultiplexer.addViewHandler( pActivity ); + + return pActivity; +} + +void RehearseTimingsActivity::start() +{ + maElapsedTime.reset(); + mbDrawPressed = false; + mbActive = true; + + // paint and show all sprites: + paintAllSprites(); + for_each_sprite( []( const ::cppcanvas::CustomSpriteSharedPtr& pSprite ) + { return pSprite->show(); } ); + + mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) ); + + mpMouseHandler->reset(); + mrEventMultiplexer.addClickHandler( + mpMouseHandler, 42 /* highest prio of all, > 3.0 */ ); + mrEventMultiplexer.addMouseMoveHandler( + mpMouseHandler, 42 /* highest prio of all, > 3.0 */ ); +} + +double RehearseTimingsActivity::stop() +{ + mrEventMultiplexer.removeMouseMoveHandler( mpMouseHandler ); + mrEventMultiplexer.removeClickHandler( mpMouseHandler ); + + mbActive = false; // will be removed from queue + + for_each_sprite( []( const ::cppcanvas::CustomSpriteSharedPtr& pSprite ) + { return pSprite->hide(); } ); + + return maElapsedTime.getElapsedTime(); +} + +bool RehearseTimingsActivity::hasBeenClicked() const +{ + if (mpMouseHandler) + return mpMouseHandler->hasBeenClicked(); + return false; +} + +// Disposable: +void RehearseTimingsActivity::dispose() +{ + stop(); + + mpWakeUpEvent.reset(); + mpMouseHandler.reset(); + + ViewsVecT().swap( maViews ); +} + +// Activity: +double RehearseTimingsActivity::calcTimeLag() const +{ + return 0.0; +} + +bool RehearseTimingsActivity::perform() +{ + if( !isActive() ) + return false; + + if( !mpWakeUpEvent ) + return false; + + mpWakeUpEvent->start(); + mpWakeUpEvent->setNextTimeout( 0.5 ); + mrEventQueue.addEvent( mpWakeUpEvent ); + + paintAllSprites(); + + // sprites changed, need screen update + mrScreenUpdater.notifyUpdate(); + + return false; // don't reinsert, WakeupEvent will perform + // that after the given timeout +} + +bool RehearseTimingsActivity::isActive() const +{ + return mbActive; +} + +void RehearseTimingsActivity::dequeued() +{ + // not used here +} + +void RehearseTimingsActivity::end() +{ + if (isActive()) + { + stop(); + mbActive = false; + } +} + +basegfx::B2DRange RehearseTimingsActivity::calcSpriteRectangle( UnoViewSharedPtr const& rView ) const +{ + const Reference<rendering::XBitmap> xBitmap( rView->getCanvas()->getUNOCanvas(), + UNO_QUERY ); + if( !xBitmap.is() ) + return basegfx::B2DRange(); + + const geometry::IntegerSize2D realSize( xBitmap->getSize() ); + // pixel: + basegfx::B2DPoint spritePos( + std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ), + std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY() + - LOWER_BORDER_SPACE ) ); + basegfx::B2DHomMatrix transformation( rView->getTransformation() ); + transformation.invert(); + spritePos *= transformation; + basegfx::B2DSize spriteSize( maSpriteSizePixel.getX(), + maSpriteSizePixel.getY() ); + spriteSize *= transformation; + return basegfx::B2DRange( + spritePos.getX(), spritePos.getY(), + spritePos.getX() + spriteSize.getX(), + spritePos.getY() + spriteSize.getY() ); +} + +void RehearseTimingsActivity::viewAdded( const UnoViewSharedPtr& rView ) +{ + cppcanvas::CustomSpriteSharedPtr sprite( + rView->createSprite( basegfx::B2DSize( + maSpriteSizePixel.getX()+2, + maSpriteSizePixel.getY()+2 ), + 1001.0 )); // sprite should be in front of all + // other sprites + sprite->setAlpha( 0.8 ); + const basegfx::B2DRange spriteRectangle( + calcSpriteRectangle( rView ) ); + sprite->move( basegfx::B2DPoint( + spriteRectangle.getMinX(), + spriteRectangle.getMinY() ) ); + + if( maViews.empty() ) + maSpriteRectangle = spriteRectangle; + + maViews.emplace_back( rView, sprite ); + + if (isActive()) + sprite->show(); +} + +void RehearseTimingsActivity::viewRemoved( const UnoViewSharedPtr& rView ) +{ + maViews.erase( + std::remove_if( maViews.begin(), maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ), + maViews.end() ); +} + +void RehearseTimingsActivity::viewChanged( const UnoViewSharedPtr& rView ) +{ + // find entry corresponding to modified view + ViewsVecT::iterator aModifiedEntry( + std::find_if( + maViews.begin(), + maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ) + ); + + OSL_ASSERT( aModifiedEntry != maViews.end() ); + if( aModifiedEntry == maViews.end() ) + return; + + // new sprite pos, transformation might have changed: + maSpriteRectangle = calcSpriteRectangle( rView ); + + // reposition sprite: + aModifiedEntry->second->move( maSpriteRectangle.getMinimum() ); + + // sprites changed, need screen update + mrScreenUpdater.notifyUpdate( rView, false ); +} + +void RehearseTimingsActivity::viewsChanged() +{ + if( maViews.empty() ) + return; + + // new sprite pos, transformation might have changed: + maSpriteRectangle = calcSpriteRectangle( maViews.front().first ); + + ::basegfx::B2DPoint nMin = maSpriteRectangle.getMinimum(); + // reposition sprites + for_each_sprite( [nMin]( const ::cppcanvas::CustomSpriteSharedPtr& pSprite ) + { return pSprite->move( nMin ); } ); + + // sprites changed, need screen update + mrScreenUpdater.notifyUpdate(); +} + +void RehearseTimingsActivity::paintAllSprites() const +{ + for_each_sprite( + [this]( const ::cppcanvas::CustomSpriteSharedPtr& pSprite ) + { return this->paint( pSprite->getContentCanvas() ); } ); +} + +void RehearseTimingsActivity::paint( cppcanvas::CanvasSharedPtr const & canvas ) const +{ + // build timer string: + const sal_Int32 nTimeSecs = + static_cast<sal_Int32>(maElapsedTime.getElapsedTime()); + OUStringBuffer buf; + sal_Int32 n = nTimeSecs / 3600; + if (n < 10) + buf.append( '0' ); + buf.append( n ); + buf.append( ':' ); + n = ((nTimeSecs % 3600) / 60); + if (n < 10) + buf.append( '0' ); + buf.append( n ); + buf.append( ':' ); + n = (nTimeSecs % 60); + if (n < 10) + buf.append( '0' ); + buf.append( n ); + const OUString time = buf.makeStringAndClear(); + + // create the MetaFile: + GDIMetaFile metaFile; + ScopedVclPtrInstance< VirtualDevice > blackHole; + metaFile.Record( blackHole ); + metaFile.SetPrefSize( Size( 1, 1 ) ); + blackHole->EnableOutput(false); + blackHole->SetMapMode(MapMode(MapUnit::MapPixel)); + blackHole->SetFont( maFont ); + tools::Rectangle rect( 0,0, + maSpriteSizePixel.getX(), + maSpriteSizePixel.getY()); + if (mbDrawPressed) + { + blackHole->SetTextColor( COL_BLACK ); + blackHole->SetFillColor( COL_LIGHTGRAY ); + blackHole->SetLineColor( COL_GRAY ); + } + else + { + blackHole->SetTextColor( COL_BLACK ); + blackHole->SetFillColor( COL_WHITE ); + blackHole->SetLineColor( COL_GRAY ); + } + blackHole->DrawRect( rect ); + blackHole->GetTextBoundRect( rect, time ); + blackHole->DrawText( + Point( (maSpriteSizePixel.getX() - rect.getWidth()) / 2, + mnYOffset ), time ); + + metaFile.Stop(); + metaFile.WindStart(); + + cppcanvas::RendererSharedPtr renderer( + cppcanvas::VCLFactory::createRenderer( + canvas, metaFile, cppcanvas::Renderer::Parameters() ) ); + const bool succ = renderer->draw(); + OSL_ASSERT( succ ); +} + + +RehearseTimingsActivity::MouseHandler::MouseHandler( RehearseTimingsActivity& rta ) : + mrActivity(rta), + mbHasBeenClicked(false), + mbMouseStartedInArea(false) +{} + +void RehearseTimingsActivity::MouseHandler::reset() +{ + mbHasBeenClicked = false; + mbMouseStartedInArea = false; +} + +bool RehearseTimingsActivity::MouseHandler::isInArea( + awt::MouseEvent const & evt ) const +{ + return mrActivity.maSpriteRectangle.isInside( + basegfx::B2DPoint( evt.X, evt.Y ) ); +} + +void RehearseTimingsActivity::MouseHandler::updatePressedState( + const bool pressedState ) const +{ + if( pressedState != mrActivity.mbDrawPressed ) + { + mrActivity.mbDrawPressed = pressedState; + mrActivity.paintAllSprites(); + + mrActivity.mrScreenUpdater.notifyUpdate(); + } +} + +// MouseEventHandler +bool RehearseTimingsActivity::MouseHandler::handleMousePressed( + awt::MouseEvent const & evt ) +{ + if( evt.Buttons == awt::MouseButton::LEFT && isInArea(evt) ) + { + mbMouseStartedInArea = true; + updatePressedState(true); + return true; // consume event + } + return false; +} + +bool RehearseTimingsActivity::MouseHandler::handleMouseReleased( + awt::MouseEvent const & evt ) +{ + if( evt.Buttons == awt::MouseButton::LEFT && mbMouseStartedInArea ) + { + mbHasBeenClicked = isInArea(evt); // fini if in + mbMouseStartedInArea = false; + updatePressedState(false); + if( !mbHasBeenClicked ) + return true; // consume event, else next slide (manual advance) + } + return false; +} + +bool RehearseTimingsActivity::MouseHandler::handleMouseDragged( + awt::MouseEvent const & evt ) +{ + if( mbMouseStartedInArea ) + updatePressedState( isInArea(evt) ); + return false; +} + +bool RehearseTimingsActivity::MouseHandler::handleMouseMoved( + awt::MouseEvent const & /*evt*/ ) +{ + return false; +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/rehearsetimingsactivity.hxx b/slideshow/source/engine/rehearsetimingsactivity.hxx new file mode 100644 index 000000000..784ebfb2f --- /dev/null +++ b/slideshow/source/engine/rehearsetimingsactivity.hxx @@ -0,0 +1,141 @@ +/* -*- 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_REHEARSETIMINGSACTIVITY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_REHEARSETIMINGSACTIVITY_HXX + +#include <activity.hxx> +#include <activitiesqueue.hxx> +#include <eventqueue.hxx> +#include <vieweventhandler.hxx> + +#include <basegfx/range/b2drange.hxx> +#include <vcl/font.hxx> + +#include <vector> +#include <utility> +#include <memory> + +namespace vcl { class Font; } +namespace canvas::tools{ class ElapsedTime; } +namespace cppcanvas{ class CustomSprite; } +namespace basegfx +{ + class B2IVector; + class B2DRange; +} + +namespace slideshow::internal { + +struct SlideShowContext; +class EventMultiplexer; +class ScreenUpdater; +class RehearseTimingsActivity : public Activity, + public ViewEventHandler +{ +public: + /** Creates the activity. + */ + static std::shared_ptr<RehearseTimingsActivity> create( + const SlideShowContext& rContext ); + + virtual ~RehearseTimingsActivity() override; + RehearseTimingsActivity(const RehearseTimingsActivity&) = delete; + RehearseTimingsActivity& operator=(const RehearseTimingsActivity&) = delete; + + /** Starts and shows the timer; adds to activity queue. + */ + void start(); + + /** Stops and hides the timer. + @return elapsed time + */ + double stop(); + + /** Determines whether the timer button has been clicked. + */ + bool hasBeenClicked() const; + + // ViewEventHandler interface + virtual void viewAdded( const UnoViewSharedPtr& rView ) override; + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override; + virtual void viewChanged( const UnoViewSharedPtr& rView ) override; + virtual void viewsChanged() override; + + // Disposable: + virtual void dispose() override; + // Activity: + virtual double calcTimeLag() const override; + virtual bool perform() override; + virtual bool isActive() const override; + virtual void dequeued() override; + virtual void end() override; + +private: + class WakeupEvent; + + explicit RehearseTimingsActivity( const SlideShowContext& rContext ); + + void paint( ::cppcanvas::CanvasSharedPtr const & canvas ) const; + void paintAllSprites() const; + + class MouseHandler; + friend class MouseHandler; + + typedef std::vector< + std::pair<UnoViewSharedPtr, + std::shared_ptr<cppcanvas::CustomSprite> > > ViewsVecT; + + template <typename func_type> + void for_each_sprite( func_type const & func ) const + { + ViewsVecT::const_iterator iPos( maViews.begin() ); + const ViewsVecT::const_iterator iEnd( maViews.end() ); + for ( ; iPos != iEnd; ++iPos ) + func( iPos->second ); + } + + ::basegfx::B2DRange calcSpriteRectangle( + UnoViewSharedPtr const & rView ) const; + + EventQueue& mrEventQueue; + ScreenUpdater& mrScreenUpdater; + EventMultiplexer& mrEventMultiplexer; + ActivitiesQueue& mrActivitiesQueue; + canvas::tools::ElapsedTime maElapsedTime; + + ViewsVecT maViews; + + /// screen rect of sprite (in view coordinates!) + ::basegfx::B2DRange maSpriteRectangle; + + vcl::Font maFont; + std::shared_ptr<WakeupEvent> mpWakeUpEvent; + std::shared_ptr<MouseHandler> mpMouseHandler; + ::basegfx::B2IVector maSpriteSizePixel; + sal_Int32 mnYOffset; + bool mbActive; + bool mbDrawPressed; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_REHEARSETIMINGSACTIVITY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/screenupdater.cxx b/slideshow/source/engine/screenupdater.cxx new file mode 100644 index 000000000..72768628d --- /dev/null +++ b/slideshow/source/engine/screenupdater.cxx @@ -0,0 +1,248 @@ +/* -*- 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 <screenupdater.hxx> +#include <listenercontainer.hxx> + +#include <osl/diagnose.h> + +#include <functional> +#include <memory> +#include <vector> +#include <algorithm> + +namespace { + class UpdateLock : public ::slideshow::internal::ScreenUpdater::UpdateLock + { + public: + explicit UpdateLock (::slideshow::internal::ScreenUpdater& rUpdater); + virtual ~UpdateLock(); + virtual void Activate() override; + private: + ::slideshow::internal::ScreenUpdater& mrUpdater; + bool mbIsActivated; + }; +} + +namespace slideshow::internal +{ + typedef std::vector< + std::pair<UnoViewSharedPtr,bool> > UpdateRequestVector; + + struct ScreenUpdater::ImplScreenUpdater + { + /** List of registered ViewUpdaters, to consult for necessary + updates + */ + ThreadUnsafeListenerContainer< + ViewUpdateSharedPtr, + std::vector<ViewUpdateSharedPtr> > maUpdaters; + + /// Views that have been notified for update + UpdateRequestVector maViewUpdateRequests; + + /// List of View. Used to issue screen updates on. + UnoViewContainer const& mrViewContainer; + + /// True, if a notifyUpdate() for all views has been issued. + bool mbUpdateAllRequest; + + /// True, if at least one notifyUpdate() call had bViewClobbered set + bool mbViewClobbered; + + /// The screen is updated only when mnLockCount==0 + sal_Int32 mnLockCount; + + explicit ImplScreenUpdater( UnoViewContainer const& rViewContainer ) : + maUpdaters(), + maViewUpdateRequests(), + mrViewContainer(rViewContainer), + mbUpdateAllRequest(false), + mbViewClobbered(false), + mnLockCount(0) + {} + }; + + ScreenUpdater::ScreenUpdater( UnoViewContainer const& rViewContainer ) : + mpImpl(new ImplScreenUpdater(rViewContainer) ) + { + } + + ScreenUpdater::~ScreenUpdater() + { + // outline because of pimpl + } + + void ScreenUpdater::notifyUpdate() + { + mpImpl->mbUpdateAllRequest = true; + } + + void ScreenUpdater::notifyUpdate( const UnoViewSharedPtr& rView, + bool bViewClobbered ) + { + mpImpl->maViewUpdateRequests.emplace_back(rView, bViewClobbered ); + + if( bViewClobbered ) + mpImpl->mbViewClobbered = true; + } + + void ScreenUpdater::commitUpdates() + { + if (mpImpl->mnLockCount > 0) + return; + + // cases: + + // (a) no update necessary at all + + // (b) no ViewUpdate-generated update + // I. update all views requested -> for_each( mrViewContainer ) + // II. update some views requested -> for_each( maViewUpdateRequests ) + + // (c) ViewUpdate-triggered update - update all views + + + // any ViewUpdate-triggered updates? + const bool bViewUpdatesNeeded( + mpImpl->maUpdaters.apply( + std::mem_fn(&ViewUpdate::needsUpdate)) ); + + if( bViewUpdatesNeeded ) + { + mpImpl->maUpdaters.applyAll( + std::mem_fn(&ViewUpdate::update) ); + } + + if( bViewUpdatesNeeded || + mpImpl->mbUpdateAllRequest ) + { + // unconditionally update all views + for( const auto& pView : mpImpl->mrViewContainer ) + { + if( mpImpl->mbViewClobbered ) + pView->paintScreen(); + else + pView->updateScreen(); + } + } + else if( !mpImpl->maViewUpdateRequests.empty() ) + { + // update notified views only + for( const auto& rViewUpdateRequest : mpImpl->maViewUpdateRequests ) + { + // TODO(P1): this is O(n^2) in the number of views, if + // lots of views notify updates. + const UnoViewVector::const_iterator aEndOfViews( + mpImpl->mrViewContainer.end() ); + UnoViewVector::const_iterator aFoundView; + if( (aFoundView=std::find(mpImpl->mrViewContainer.begin(), + aEndOfViews, + rViewUpdateRequest.first)) != aEndOfViews ) + { + if( rViewUpdateRequest.second ) + (*aFoundView)->paintScreen(); // force-paint + else + (*aFoundView)->updateScreen(); // update changes only + } + } + } + + // done - clear requests + mpImpl->mbViewClobbered = false; + mpImpl->mbUpdateAllRequest = false; + UpdateRequestVector().swap( mpImpl->maViewUpdateRequests ); + } + + void ScreenUpdater::addViewUpdate( ViewUpdateSharedPtr const& rViewUpdate ) + { + mpImpl->maUpdaters.add( rViewUpdate ); + } + + void ScreenUpdater::removeViewUpdate( ViewUpdateSharedPtr const& rViewUpdate ) + { + mpImpl->maUpdaters.remove( rViewUpdate ); + } + + void ScreenUpdater::requestImmediateUpdate() + { + if (mpImpl->mnLockCount > 0) + return; + + // TODO(F2): This will interfere with other updates, since it + // happens out-of-sync with main animation loop. Might cause + // artifacts. + for( auto const& pView : mpImpl->mrViewContainer ) + pView->updateScreen(); + } + + void ScreenUpdater::lockUpdates() + { + ++mpImpl->mnLockCount; + OSL_ASSERT(mpImpl->mnLockCount>0); + } + + void ScreenUpdater::unlockUpdates() + { + OSL_ASSERT(mpImpl->mnLockCount>0); + if (mpImpl->mnLockCount > 0) + { + --mpImpl->mnLockCount; + if (mpImpl->mnLockCount) + commitUpdates(); + } + } + + ::std::shared_ptr<ScreenUpdater::UpdateLock> ScreenUpdater::createLock() + { + return ::std::make_shared<::UpdateLock>(*this); + } + + +} // namespace slideshow::internal + +namespace { + +UpdateLock::UpdateLock ( + ::slideshow::internal::ScreenUpdater& rUpdater) + : mrUpdater(rUpdater), + mbIsActivated(false) +{ +} + + +UpdateLock::~UpdateLock() +{ + if (mbIsActivated) + mrUpdater.unlockUpdates(); +} + + +void UpdateLock::Activate() +{ + if ( ! mbIsActivated) + { + mbIsActivated = true; + mrUpdater.lockUpdates(); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapeattributelayer.cxx b/slideshow/source/engine/shapeattributelayer.cxx new file mode 100644 index 000000000..25ac6a41c --- /dev/null +++ b/slideshow/source/engine/shapeattributelayer.cxx @@ -0,0 +1,810 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <shapeattributelayer.hxx> + +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/animations/AnimationAdditiveMode.hpp> + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + /** Update state ids + + This method updates all state IDs from possible + children. Whenever a child's state ID changed, we + increment ours. + */ + void ShapeAttributeLayer::updateStateIds() + { + if( !haveChild() ) + return; + + if( mnTransformationState != mpChild->getTransformationState() ) + ++mnTransformationState; + if( mnClipState != mpChild->getClipState() ) + ++mnClipState; + if( mnAlphaState != mpChild->getAlphaState() ) + ++mnAlphaState; + if( mnPositionState != mpChild->getPositionState() ) + ++mnPositionState; + if( mnContentState != mpChild->getContentState() ) + ++mnContentState; + if( mnVisibilityState != mpChild->getVisibilityState() ) + ++mnVisibilityState; + } + + /** Calc attribute value. + + This method determines the current attribute value, + appropriately combining it with children values (by + evaluating the mnAdditiveMode member). + */ + template< typename T > T ShapeAttributeLayer::calcValue( const T& rCurrValue, + bool bThisInstanceValid, + bool (ShapeAttributeLayer::*pIsValid)() const, + T (ShapeAttributeLayer::*pGetValue)() const ) const + { + // deviated from the (*shared_ptr).*mpFuncPtr notation + // here, since gcc does not seem to parse that as a member + // function call anymore. + const bool bChildInstanceValueValid( haveChild() && (mpChild.get()->*pIsValid)() ); + + if( bThisInstanceValid ) + { + if( bChildInstanceValueValid ) + { + // merge with child value + switch( mnAdditiveMode ) + { + default: + // FALTHROUGH intended + case animations::AnimationAdditiveMode::NONE: + // FALTHROUGH intended + case animations::AnimationAdditiveMode::BASE: + // FALTHROUGH intended + case animations::AnimationAdditiveMode::REPLACE: + // TODO(F2): reverse-engineer the semantics of these + // values + + // currently, treat them the same and replace + // the child value by our own + return rCurrValue; + + case animations::AnimationAdditiveMode::SUM: + return rCurrValue + ((*mpChild).*pGetValue)(); + + case animations::AnimationAdditiveMode::MULTIPLY: + return rCurrValue * ((*mpChild).*pGetValue)(); + } + } + else + { + // this object is the only one defining + // the value, so take it + return rCurrValue; + } + } + else + { + return bChildInstanceValueValid ? + ((*mpChild).*pGetValue)() : + T(); // pass on child value, regardless + // if it's valid or not. If not, it's + // a default anyway + } + } + + ShapeAttributeLayer::ShapeAttributeLayer( const ShapeAttributeLayerSharedPtr& rChildLayer ) : + mpChild( rChildLayer ), + + maSize(), + maPosition(), + maClip(), + + maFontFamily(), + + mnRotationAngle(), + mnShearXAngle(), + mnShearYAngle(), + mnAlpha(), + mnCharScale(), + mnCharWeight(), + + meFillStyle( drawing::FillStyle_NONE ), + meLineStyle( drawing::LineStyle_NONE ), + meCharPosture( awt::FontSlant_NONE ), + mnUnderlineMode(), + + maDimColor(), + maFillColor(), + maLineColor(), + maCharColor(), + + mnTransformationState( rChildLayer ? rChildLayer->getTransformationState() : 0 ), + mnClipState( rChildLayer ? rChildLayer->getClipState() : 0), + mnAlphaState( rChildLayer ? rChildLayer->getAlphaState() : 0), + mnPositionState( rChildLayer ? rChildLayer->getPositionState() : 0 ), + mnContentState( rChildLayer ? rChildLayer->getContentState() : 0 ), + mnVisibilityState( rChildLayer ? rChildLayer->getVisibilityState() : 0 ), + + mnAdditiveMode( animations::AnimationAdditiveMode::BASE ), + + mbVisibility( false ), + + mbWidthValid( false ), + mbHeightValid( false ), + mbPosXValid( false ), + mbPosYValid( false ), + mbClipValid( false ), + + mbFontFamilyValid( false ), + + mbRotationAngleValid( false ), + mbShearXAngleValid( false ), + mbShearYAngleValid( false ), + + mbAlphaValid( false ), + + mbCharScaleValid( false ), + + mbDimColorValid( false ), + mbFillColorValid( false ), + mbLineColorValid( false ), + mbCharColorValid( false ), + + mbFillStyleValid( false ), + mbLineStyleValid( false ), + mbCharWeightValid( false ), + mbUnderlineModeValid( false ), + mbCharPostureValid( false ), + mbVisibilityValid( false ) + { + } + + bool ShapeAttributeLayer::revokeChildLayer( const ShapeAttributeLayerSharedPtr& rChildLayer ) + { + ENSURE_OR_RETURN_FALSE( rChildLayer, + "ShapeAttributeLayer::revokeChildLayer(): Will not remove NULL child" ); + + if( !haveChild() ) + return false; // no children, nothing to revoke. + + if( mpChild == rChildLayer ) + { + // we have it - replace by removed child's sibling. + mpChild = rChildLayer->getChildLayer(); + + // if we're now the first one, defensively increment _all_ + // state ids: possibly all underlying attributes have now + // changed to default + if( !haveChild() ) + { + // TODO(P1): Check whether it pays off to check more + // detailed, which attributes really change + ++mnTransformationState; + ++mnClipState; + ++mnAlphaState; + ++mnPositionState; + ++mnContentState; + ++mnVisibilityState; + } + } + else + { + // we don't have it - pass on the request + if( !mpChild->revokeChildLayer( rChildLayer ) ) + return false; // nobody has it - bail out + } + + // something might have changed - update ids. + updateStateIds(); + + return true; + } + + const ShapeAttributeLayerSharedPtr& ShapeAttributeLayer::getChildLayer() const + { + return mpChild; + } + + void ShapeAttributeLayer::setAdditiveMode( sal_Int16 nMode ) + { + if( mnAdditiveMode != nMode ) + { + // TODO(P1): Check whether it pays off to check more + // detailed, which attributes really change + + // defensively increment all states - possibly each of them + // will change with different additive mode + ++mnTransformationState; + ++mnClipState; + ++mnAlphaState; + ++mnPositionState; + ++mnContentState; + ++mnVisibilityState; + } + + mnAdditiveMode = nMode; + } + + bool ShapeAttributeLayer::isWidthValid() const + { + return mbWidthValid || (haveChild() && mpChild->isWidthValid()); + } + + double ShapeAttributeLayer::getWidth() const + { + return calcValue< double >( + maSize.getX(), + mbWidthValid, + &ShapeAttributeLayer::isWidthValid, + &ShapeAttributeLayer::getWidth ); + } + + void ShapeAttributeLayer::setWidth( const double& rNewWidth ) + { + ENSURE_OR_THROW( std::isfinite(rNewWidth), + "ShapeAttributeLayer::setWidth(): Invalid width" ); + + maSize.setX( rNewWidth ); + mbWidthValid = true; + ++mnTransformationState; + } + + bool ShapeAttributeLayer::isHeightValid() const + { + return mbHeightValid || ( haveChild() && mpChild->isHeightValid() ); + } + + double ShapeAttributeLayer::getHeight() const + { + return calcValue< double >( + maSize.getY(), + mbHeightValid, + &ShapeAttributeLayer::isHeightValid, + &ShapeAttributeLayer::getHeight ); + } + + void ShapeAttributeLayer::setHeight( const double& rNewHeight ) + { + ENSURE_OR_THROW( std::isfinite(rNewHeight), + "ShapeAttributeLayer::setHeight(): Invalid height" ); + + maSize.setY( rNewHeight ); + mbHeightValid = true; + ++mnTransformationState; + } + + void ShapeAttributeLayer::setSize( const ::basegfx::B2DSize& rNewSize ) + { + ENSURE_OR_THROW( std::isfinite(rNewSize.getX()) && + std::isfinite(rNewSize.getY()), + "ShapeAttributeLayer::setSize(): Invalid size" ); + + maSize = rNewSize; + mbWidthValid = mbHeightValid = true; + ++mnTransformationState; + } + + bool ShapeAttributeLayer::isPosXValid() const + { + return mbPosXValid || ( haveChild() && mpChild->isPosXValid() ); + } + + double ShapeAttributeLayer::getPosX() const + { + return calcValue< double >( + maPosition.getX(), + mbPosXValid, + &ShapeAttributeLayer::isPosXValid, + &ShapeAttributeLayer::getPosX ); + } + + void ShapeAttributeLayer::setPosX( const double& rNewX ) + { + ENSURE_OR_THROW( std::isfinite(rNewX), + "ShapeAttributeLayer::setPosX(): Invalid position" ); + + maPosition.setX( rNewX ); + mbPosXValid = true; + ++mnPositionState; + } + + bool ShapeAttributeLayer::isPosYValid() const + { + return mbPosYValid || ( haveChild() && mpChild->isPosYValid() ); + } + + double ShapeAttributeLayer::getPosY() const + { + return calcValue< double >( + maPosition.getY(), + mbPosYValid, + &ShapeAttributeLayer::isPosYValid, + &ShapeAttributeLayer::getPosY ); + } + + void ShapeAttributeLayer::setPosY( const double& rNewY ) + { + ENSURE_OR_THROW( std::isfinite(rNewY), + "ShapeAttributeLayer::setPosY(): Invalid position" ); + + maPosition.setY( rNewY ); + mbPosYValid = true; + ++mnPositionState; + } + + void ShapeAttributeLayer::setPosition( const ::basegfx::B2DPoint& rNewPos ) + { + maPosition = rNewPos; + mbPosXValid = mbPosYValid = true; + ++mnPositionState; + } + + bool ShapeAttributeLayer::isRotationAngleValid() const + { + return mbRotationAngleValid || ( haveChild() && mpChild->isRotationAngleValid() ); + } + + double ShapeAttributeLayer::getRotationAngle() const + { + return calcValue< double >( + mnRotationAngle, + mbRotationAngleValid, + &ShapeAttributeLayer::isRotationAngleValid, + &ShapeAttributeLayer::getRotationAngle ); + } + + void ShapeAttributeLayer::setRotationAngle( const double& rNewAngle ) + { + ENSURE_OR_THROW( std::isfinite(rNewAngle), + "ShapeAttributeLayer::setRotationAngle(): Invalid angle" ); + + mnRotationAngle = rNewAngle; + mbRotationAngleValid = true; + ++mnTransformationState; + } + + bool ShapeAttributeLayer::isShearXAngleValid() const + { + return mbShearXAngleValid || ( haveChild() && mpChild->isShearXAngleValid() ); + } + + double ShapeAttributeLayer::getShearXAngle() const + { + return calcValue( mnShearXAngle, + mbShearXAngleValid, + &ShapeAttributeLayer::isShearXAngleValid, + &ShapeAttributeLayer::getShearXAngle ); + } + + void ShapeAttributeLayer::setShearXAngle( const double& rNewAngle ) + { + ENSURE_OR_THROW( std::isfinite(rNewAngle), + "ShapeAttributeLayer::setShearXAngle(): Invalid angle" ); + + mnShearXAngle = rNewAngle; + mbShearXAngleValid = true; + ++mnTransformationState; + } + + bool ShapeAttributeLayer::isShearYAngleValid() const + { + return mbShearYAngleValid || ( haveChild() && mpChild->isShearYAngleValid() ); + } + + double ShapeAttributeLayer::getShearYAngle() const + { + return calcValue( mnShearYAngle, + mbShearYAngleValid, + &ShapeAttributeLayer::isShearYAngleValid, + &ShapeAttributeLayer::getShearYAngle ); + } + + void ShapeAttributeLayer::setShearYAngle( const double& rNewAngle ) + { + ENSURE_OR_THROW( std::isfinite(rNewAngle), + "ShapeAttributeLayer::setShearYAngle(): Invalid angle" ); + + mnShearYAngle = rNewAngle; + mbShearYAngleValid = true; + ++mnTransformationState; + } + + bool ShapeAttributeLayer::isAlphaValid() const + { + return mbAlphaValid || ( haveChild() && mpChild->isAlphaValid() ); + } + + double ShapeAttributeLayer::getAlpha() const + { + return calcValue( mnAlpha, + mbAlphaValid, + &ShapeAttributeLayer::isAlphaValid, + &ShapeAttributeLayer::getAlpha ); + } + + void ShapeAttributeLayer::setAlpha( const double& rNewValue ) + { + ENSURE_OR_THROW( std::isfinite(rNewValue), + "ShapeAttributeLayer::setAlpha(): Invalid alpha" ); + + mnAlpha = rNewValue; + mbAlphaValid = true; + ++mnAlphaState; + } + + bool ShapeAttributeLayer::isClipValid() const + { + return mbClipValid || ( haveChild() && mpChild->isClipValid() ); + } + + ::basegfx::B2DPolyPolygon ShapeAttributeLayer::getClip() const + { + // TODO(F1): Implement polygon algebra for additive modes + if( mbClipValid ) + return maClip; + else if( haveChild() ) + return mpChild->getClip(); + else + return ::basegfx::B2DPolyPolygon(); + } + + void ShapeAttributeLayer::setClip( const ::basegfx::B2DPolyPolygon& rNewClip ) + { + maClip = rNewClip; + mbClipValid = true; + ++mnClipState; + } + + bool ShapeAttributeLayer::isDimColorValid() const + { + return mbDimColorValid || ( haveChild() && mpChild->isDimColorValid() ); + } + + RGBColor ShapeAttributeLayer::getDimColor() const + { + return calcValue( maDimColor, + mbDimColorValid, + &ShapeAttributeLayer::isDimColorValid, + &ShapeAttributeLayer::getDimColor ); + } + + void ShapeAttributeLayer::setDimColor( const RGBColor& nNewColor ) + { + maDimColor = nNewColor; + mbDimColorValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isFillColorValid() const + { + return mbFillColorValid || ( haveChild() && mpChild->isFillColorValid() ); + } + + RGBColor ShapeAttributeLayer::getFillColor() const + { + return calcValue( maFillColor, + mbFillColorValid, + &ShapeAttributeLayer::isFillColorValid, + &ShapeAttributeLayer::getFillColor ); + } + + void ShapeAttributeLayer::setFillColor( const RGBColor& nNewColor ) + { + maFillColor = nNewColor; + mbFillColorValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isLineColorValid() const + { + return mbLineColorValid || ( haveChild() && mpChild->isLineColorValid() ); + } + + RGBColor ShapeAttributeLayer::getLineColor() const + { + return calcValue( maLineColor, + mbLineColorValid, + &ShapeAttributeLayer::isLineColorValid, + &ShapeAttributeLayer::getLineColor ); + } + + void ShapeAttributeLayer::setLineColor( const RGBColor& nNewColor ) + { + maLineColor = nNewColor; + mbLineColorValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isFillStyleValid() const + { + return mbFillStyleValid || ( haveChild() && mpChild->isFillStyleValid() ); + } + + sal_Int16 ShapeAttributeLayer::getFillStyle() const + { + // mnAdditiveMode is ignored, cannot combine strings in + // any sensible way + if( mbFillStyleValid ) + return sal::static_int_cast<sal_Int16>(meFillStyle); + else if( haveChild() ) + return sal::static_int_cast<sal_Int16>(mpChild->getFillStyle()); + else + return sal::static_int_cast<sal_Int16>(drawing::FillStyle_SOLID); + } + + void ShapeAttributeLayer::setFillStyle( const sal_Int16& rStyle ) + { + // TODO(Q1): Check range here. + meFillStyle = static_cast<drawing::FillStyle>(rStyle); + mbFillStyleValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isLineStyleValid() const + { + return mbLineStyleValid || ( haveChild() && mpChild->isLineStyleValid() ); + } + + sal_Int16 ShapeAttributeLayer::getLineStyle() const + { + // mnAdditiveMode is ignored, cannot combine strings in + // any sensible way + if( mbLineStyleValid ) + return sal::static_int_cast<sal_Int16>(meLineStyle); + else if( haveChild() ) + return sal::static_int_cast<sal_Int16>(mpChild->getLineStyle()); + else + return sal::static_int_cast<sal_Int16>(drawing::LineStyle_SOLID); + } + + void ShapeAttributeLayer::setLineStyle( const sal_Int16& rStyle ) + { + // TODO(Q1): Check range here. + meLineStyle = static_cast<drawing::LineStyle>(rStyle); + mbLineStyleValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isVisibilityValid() const + { + return mbVisibilityValid || ( haveChild() && mpChild->isVisibilityValid() ); + } + + bool ShapeAttributeLayer::getVisibility() const + { + // mnAdditiveMode is ignored, SMIL spec requires to not combine + // bools in any sensible way + if( mbVisibilityValid ) + return mbVisibility; + else if( haveChild() ) + return mpChild->getVisibility(); + else + return true; // default is always visible + } + + void ShapeAttributeLayer::setVisibility( const bool& bVisible ) + { + mbVisibility = bVisible; + mbVisibilityValid = true; + ++mnVisibilityState; + } + + bool ShapeAttributeLayer::isCharColorValid() const + { + return mbCharColorValid || ( haveChild() && mpChild->isCharColorValid() ); + } + + RGBColor ShapeAttributeLayer::getCharColor() const + { + return calcValue( maCharColor, + mbCharColorValid, + &ShapeAttributeLayer::isCharColorValid, + &ShapeAttributeLayer::getCharColor ); + } + + void ShapeAttributeLayer::setCharColor( const RGBColor& nNewColor ) + { + maCharColor = nNewColor; + mbCharColorValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isCharWeightValid() const + { + return mbCharWeightValid || ( haveChild() && mpChild->isCharWeightValid() ); + } + + double ShapeAttributeLayer::getCharWeight() const + { + // mnAdditiveMode is ignored, cannot combine strings in + // any sensible way + if( mbCharWeightValid ) + return mnCharWeight; + else if( haveChild() ) + return mpChild->getCharWeight(); + else + return awt::FontWeight::NORMAL; + } + + void ShapeAttributeLayer::setCharWeight( const double& rValue ) + { + // TODO(Q1): Check range here. + mnCharWeight = rValue; + mbCharWeightValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isUnderlineModeValid() const + { + return mbUnderlineModeValid || ( haveChild() && mpChild->isUnderlineModeValid() ); + } + + sal_Int16 ShapeAttributeLayer::getUnderlineMode() const + { + // mnAdditiveMode is ignored, SMIL spec requires to not combine + // bools in any sensible way + if( mbUnderlineModeValid ) + return mnUnderlineMode; + else if( haveChild() ) + return mpChild->getUnderlineMode(); + else + return awt::FontUnderline::NONE; // default is no underline + } + + void ShapeAttributeLayer::setUnderlineMode( const sal_Int16& rUnderlineMode ) + { + // TODO(Q1): Check range here. + mnUnderlineMode = rUnderlineMode; + mbUnderlineModeValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isFontFamilyValid() const + { + return mbFontFamilyValid || ( haveChild() && mpChild->isFontFamilyValid() ); + } + + OUString ShapeAttributeLayer::getFontFamily() const + { + // mnAdditiveMode is ignored, cannot combine strings in + // any sensible way + if( mbFontFamilyValid ) + return maFontFamily; + else if( haveChild() ) + return mpChild->getFontFamily(); + else + return OUString(); + } + + void ShapeAttributeLayer::setFontFamily( const OUString& rName ) + { + maFontFamily = rName; + mbFontFamilyValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isCharPostureValid() const + { + return mbCharPostureValid || ( haveChild() && mpChild->isCharPostureValid() ); + } + + sal_Int16 ShapeAttributeLayer::getCharPosture() const + { + // mnAdditiveMode is ignored, cannot combine strings in + // any sensible way + if( mbCharPostureValid ) + return sal::static_int_cast<sal_Int16>(meCharPosture); + else if( haveChild() ) + return sal::static_int_cast<sal_Int16>(mpChild->getCharPosture()); + else + return sal::static_int_cast<sal_Int16>(awt::FontSlant_NONE); + } + + void ShapeAttributeLayer::setCharPosture( const sal_Int16& rStyle ) + { + // TODO(Q1): Check range here. + meCharPosture = static_cast<awt::FontSlant>(rStyle); + mbCharPostureValid = true; + ++mnContentState; + } + + bool ShapeAttributeLayer::isCharScaleValid() const + { + return mbCharScaleValid || ( haveChild() && mpChild->isCharScaleValid() ); + } + + double ShapeAttributeLayer::getCharScale() const + { + return calcValue( mnCharScale, + mbCharScaleValid, + &ShapeAttributeLayer::isCharScaleValid, + &ShapeAttributeLayer::getCharScale ); + } + + void ShapeAttributeLayer::setCharScale( const double& rNewHeight ) + { + ENSURE_OR_THROW( std::isfinite(rNewHeight), + "ShapeAttributeLayer::setCharScale(): Invalid height" ); + + mnCharScale = rNewHeight; + mbCharScaleValid = true; + ++mnContentState; + } + + State::StateId ShapeAttributeLayer::getTransformationState() const + { + return haveChild() ? + ::std::max( mnTransformationState, + mpChild->getTransformationState() ) : + mnTransformationState; + } + + State::StateId ShapeAttributeLayer::getClipState() const + { + return haveChild() ? + ::std::max( mnClipState, + mpChild->getClipState() ) : + mnClipState; + } + + State::StateId ShapeAttributeLayer::getAlphaState() const + { + return haveChild() ? + ::std::max( mnAlphaState, + mpChild->getAlphaState() ) : + mnAlphaState; + } + + State::StateId ShapeAttributeLayer::getPositionState() const + { + return haveChild() ? + ::std::max( mnPositionState, + mpChild->getPositionState() ) : + mnPositionState; + } + + State::StateId ShapeAttributeLayer::getContentState() const + { + return haveChild() ? + ::std::max( mnContentState, + mpChild->getContentState() ) : + mnContentState; + } + + State::StateId ShapeAttributeLayer::getVisibilityState() const + { + return haveChild() ? + ::std::max( mnVisibilityState, + mpChild->getVisibilityState() ) : + mnVisibilityState; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/appletshape.cxx b/slideshow/source/engine/shapes/appletshape.cxx new file mode 100644 index 000000000..ba7c6243b --- /dev/null +++ b/slideshow/source/engine/shapes/appletshape.cxx @@ -0,0 +1,288 @@ +/* -*- 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 "appletshape.hxx" +#include "externalshapebase.hxx" +#include "viewappletshape.hxx" +#include <tools.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + namespace { + + /** Represents an applet shape. + + This implementation offers support for applet shapes (both + Java applets, and Netscape plugins). Such shapes need + special treatment. + */ + class AppletShape : public ExternalShapeBase + { + public: + /** Create a shape for the given XShape for an applet object + + @param xShape + The XShape to represent. + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + + @param rServiceName + Service name to use, when creating the actual viewer + component + + @param pPropCopyTable + Table of plain ASCII property names, to copy from + xShape to applet. + + @param nNumPropEntries + Number of property table entries (in pPropCopyTable) + */ + AppletShape( const css::uno::Reference< css::drawing::XShape >& xShape, + double nPrio, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + private: + + // View layer methods + + + virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) override; + virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override; + virtual void clearAllViewLayers() override; + + + // ExternalShapeBase methods + + + virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const override; + virtual void implViewChanged( const UnoViewSharedPtr& rView ) override; + virtual void implViewsChanged() override; + virtual bool implStartIntrinsicAnimation() override; + virtual bool implEndIntrinsicAnimation() override; + virtual void implPauseIntrinsicAnimation() override; + virtual bool implIsIntrinsicAnimationPlaying() const override; + virtual void implSetIntrinsicAnimationTime(double) override; + + const OUString maServiceName; + const char** mpPropCopyTable; + const std::size_t mnNumPropEntries; + + /// the list of active view shapes (one for each registered view layer) + typedef ::std::vector< ViewAppletShapeSharedPtr > ViewAppletShapeVector; + ViewAppletShapeVector maViewAppletShapes; + bool mbIsPlaying; + }; + + } + + AppletShape::AppletShape( const uno::Reference< drawing::XShape >& xShape, + double nPrio, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const SlideShowContext& rContext ) : + ExternalShapeBase( xShape, nPrio, rContext ), + maServiceName( rServiceName ), + mpPropCopyTable( pPropCopyTable ), + mnNumPropEntries( nNumPropEntries ), + maViewAppletShapes(), + mbIsPlaying(false) + { + } + + + void AppletShape::implViewChanged( const UnoViewSharedPtr& rView ) + { + const ::basegfx::B2DRectangle& rBounds = getBounds(); + // determine ViewAppletShape that needs update + for( const auto& pViewAppletShape : maViewAppletShapes ) + { + if( pViewAppletShape->getViewLayer()->isOnView( rView ) ) + pViewAppletShape->resize( rBounds ); + } + } + + + void AppletShape::implViewsChanged() + { + // resize all ViewShapes + const ::basegfx::B2DRectangle& rBounds = getBounds(); + for( const auto& pViewAppletShape : maViewAppletShapes ) + pViewAppletShape->resize( rBounds ); + } + + + void AppletShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) + { + try + { + maViewAppletShapes.push_back( + std::make_shared<ViewAppletShape>( rNewLayer, + getXShape(), + maServiceName, + mpPropCopyTable, + mnNumPropEntries, + mxComponentContext )); + + // push new size to view shape + maViewAppletShapes.back()->resize( getBounds() ); + + // render the Shape on the newly added ViewLayer + if( bRedrawLayer ) + maViewAppletShapes.back()->render( getBounds() ); + } + catch(uno::Exception&) + { + // ignore failed shapes - slideshow should run with + // the remaining content + } + } + + + bool AppletShape::removeViewLayer( const ViewLayerSharedPtr& rLayer ) + { + const ViewAppletShapeVector::iterator aEnd( maViewAppletShapes.end() ); + + OSL_ENSURE( ::std::count_if(maViewAppletShapes.begin(), + aEnd, + [&rLayer] + ( const ViewAppletShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) < 2, + "AppletShape::removeViewLayer(): Duplicate ViewLayer entries!" ); + + ViewAppletShapeVector::iterator aIter; + + if( (aIter=::std::remove_if( maViewAppletShapes.begin(), + aEnd, + [&rLayer] + ( const ViewAppletShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) ) == aEnd ) + { + // view layer seemingly was not added, failed + return false; + } + + // actually erase from container + maViewAppletShapes.erase( aIter, aEnd ); + + return true; + } + + + void AppletShape::clearAllViewLayers() + { + maViewAppletShapes.clear(); + } + + + bool AppletShape::implRender( const ::basegfx::B2DRange& rCurrBounds ) const + { + // redraw all view shapes, by calling their update() method + if( o3tl::make_unsigned(::std::count_if( maViewAppletShapes.begin(), + maViewAppletShapes.end(), + [&rCurrBounds] + ( const ViewAppletShapeSharedPtr& pShape ) + { return pShape->render( rCurrBounds ); } )) + != maViewAppletShapes.size() ) + { + // at least one of the ViewShape::update() calls did return + // false - update failed on at least one ViewLayer + return false; + } + + return true; + } + + + bool AppletShape::implStartIntrinsicAnimation() + { + const ::basegfx::B2DRectangle& rBounds = getBounds(); + for( const auto& pViewAppletShape : maViewAppletShapes ) + pViewAppletShape->startApplet( rBounds ); + + mbIsPlaying = true; + + return true; + } + + + bool AppletShape::implEndIntrinsicAnimation() + { + for( const auto& pViewAppletShape : maViewAppletShapes ) + pViewAppletShape->endApplet(); + + mbIsPlaying = false; + + return true; + } + + + void AppletShape::implPauseIntrinsicAnimation() + { + // TODO(F1): any way of temporarily disabling/deactivating + // applets? + } + + + bool AppletShape::implIsIntrinsicAnimationPlaying() const + { + return mbIsPlaying; + } + + + void AppletShape::implSetIntrinsicAnimationTime(double) + { + // No way of doing this, or? + } + + std::shared_ptr<Shape> createAppletShape( + const uno::Reference< drawing::XShape >& xShape, + double nPrio, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const SlideShowContext& rContext ) + { + return std::make_shared<AppletShape>(xShape, + nPrio, + rServiceName, + pPropCopyTable, + nNumPropEntries, + rContext); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/appletshape.hxx b/slideshow/source/engine/shapes/appletshape.hxx new file mode 100644 index 000000000..0de63af2c --- /dev/null +++ b/slideshow/source/engine/shapes/appletshape.hxx @@ -0,0 +1,44 @@ +/* -*- 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_SHAPES_APPLETSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_APPLETSHAPE_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <memory> + +namespace com::sun::star::drawing { class XShape; } + +namespace slideshow::internal +{ + struct SlideShowContext; + class Shape; + + std::shared_ptr<Shape> createAppletShape( + const css::uno::Reference< css::drawing::XShape >& xShape, + double nPrio, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const SlideShowContext& rContext ); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_APPLETSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/backgroundshape.cxx b/slideshow/source/engine/shapes/backgroundshape.cxx new file mode 100644 index 000000000..d304b9f90 --- /dev/null +++ b/slideshow/source/engine/shapes/backgroundshape.cxx @@ -0,0 +1,299 @@ +/* -*- 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/beans/XPropertySet.hpp> + +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + +#include "backgroundshape.hxx" +#include <slideshowexceptions.hxx> +#include <slideshowcontext.hxx> +#include "gdimtftools.hxx" +#include <shape.hxx> +#include "viewbackgroundshape.hxx" + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + namespace { + + /** Representation of a draw document's background shape. + + This class implements the Shape interface for the + background shape. Since the background shape is neither + animatable nor attributable, those more specialized + derivations of the Shape interface are not implemented + here. + + @attention this class is to be treated 'final', i.e. one + should not derive from it. + */ + class BackgroundShape : public Shape + { + public: + /** Create the background shape. + + This method creates a shape that handles the + peculiarities of the draw API regarding background + content. + */ + BackgroundShape( const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage, + const css::uno::Reference< css::drawing::XDrawPage >& xMasterPage, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + virtual css::uno::Reference< + css::drawing::XShape > getXShape() const override; + + // View layer methods + + + virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) override; + virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override; + virtual void clearAllViewLayers() override; + + + // attribute methods + + + virtual ::basegfx::B2DRectangle getBounds() const override; + virtual ::basegfx::B2DRectangle getDomBounds() const override; + virtual ::basegfx::B2DRectangle getUpdateArea() const override; + virtual bool isVisible() const override; + virtual double getPriority() const override; + virtual bool isForeground() const override { return false; } + virtual bool isBackgroundDetached() const override; + + + // render methods + + + virtual bool update() const override; + virtual bool render() const override; + virtual bool isContentChanged() const override; + + private: + /// The metafile actually representing the Shape + GDIMetaFileSharedPtr mpMtf; + + // The attributes of this Shape + ::basegfx::B2DRectangle maBounds; // always needed for rendering + + /// the list of active view shapes (one for each registered view layer) + typedef ::std::vector< ViewBackgroundShapeSharedPtr > ViewBackgroundShapeVector; + ViewBackgroundShapeVector maViewShapes; + }; + + } + + BackgroundShape::BackgroundShape( const uno::Reference< drawing::XDrawPage >& xDrawPage, + const uno::Reference< drawing::XDrawPage >& xMasterPage, + const SlideShowContext& rContext ) : + mpMtf(), + maBounds(), + maViewShapes() + { + uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, + uno::UNO_QUERY_THROW ); + // first try the page background (overrides + // masterpage background), then try masterpage + GDIMetaFileSharedPtr xMtf = getMetaFile(uno::Reference<lang::XComponent>(xDrawPage, uno::UNO_QUERY), + xDrawPage, MTF_LOAD_BACKGROUND_ONLY, + rContext.mxComponentContext); + + if (!xMtf) + { + xMtf = getMetaFile( uno::Reference<lang::XComponent>(xMasterPage, uno::UNO_QUERY), + xDrawPage, MTF_LOAD_BACKGROUND_ONLY, + rContext.mxComponentContext ); + } + + if (!xMtf) + { + throw ShapeLoadFailedException(); + } + + // there is a special background shape, add it + // as the first one + + sal_Int32 nDocWidth=0; + sal_Int32 nDocHeight=0; + xPropSet->getPropertyValue("Width") >>= nDocWidth; + xPropSet->getPropertyValue("Height") >>= nDocHeight; + + mpMtf = xMtf; + maBounds = ::basegfx::B2DRectangle( 0,0,nDocWidth, nDocHeight ); + } + + uno::Reference< drawing::XShape > BackgroundShape::getXShape() const + { + // no real XShape representative + return uno::Reference< drawing::XShape >(); + } + + void BackgroundShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) + { + // already added? + if( ::std::any_of( maViewShapes.begin(), + maViewShapes.end(), + [&rNewLayer]( const ViewBackgroundShapeSharedPtr& pBgShape ) + { return pBgShape->getViewLayer() == rNewLayer; } ) ) + { + // yes, nothing to do + return; + } + + maViewShapes.push_back( + std::make_shared<ViewBackgroundShape>( + rNewLayer, maBounds ) ); + + // render the Shape on the newly added ViewLayer + if( bRedrawLayer ) + maViewShapes.back()->render( mpMtf ); + } + + bool BackgroundShape::removeViewLayer( const ViewLayerSharedPtr& rLayer ) + { + const ViewBackgroundShapeVector::iterator aEnd( maViewShapes.end() ); + + OSL_ENSURE( ::std::count_if(maViewShapes.begin(), + aEnd, + [&rLayer]( const ViewBackgroundShapeSharedPtr& pBgShape ) + { return pBgShape->getViewLayer() == rLayer; } ) < 2, + "BackgroundShape::removeViewLayer(): Duplicate ViewLayer entries!" ); + + ViewBackgroundShapeVector::iterator aIter; + + if( (aIter=::std::remove_if( maViewShapes.begin(), + aEnd, + [&rLayer]( const ViewBackgroundShapeSharedPtr& pBgShape ) + { return pBgShape->getViewLayer() == rLayer; } )) == aEnd ) + { + // view layer seemingly was not added, failed + return false; + } + + // actually erase from container + maViewShapes.erase( aIter, aEnd ); + + return true; + } + + void BackgroundShape::clearAllViewLayers() + { + maViewShapes.clear(); + } + + ::basegfx::B2DRectangle BackgroundShape::getBounds() const + { + return maBounds; + } + + ::basegfx::B2DRectangle BackgroundShape::getDomBounds() const + { + return maBounds; + } + + ::basegfx::B2DRectangle BackgroundShape::getUpdateArea() const + { + // TODO(F1): Need to expand background, too, when + // antialiasing? + + // no transformation etc. possible for background shape + return maBounds; + } + + bool BackgroundShape::isVisible() const + { + return true; + } + + double BackgroundShape::getPriority() const + { + return 0.0; // lowest prio, we're the background + } + + bool BackgroundShape::update() const + { + return render(); + } + + bool BackgroundShape::render() const + { + SAL_INFO( "slideshow", "::presentation::internal::BackgroundShape::render()" ); + SAL_INFO( "slideshow", "::presentation::internal::BackgroundShape: 0x" << std::hex << this ); + + // gcc again... + const ::basegfx::B2DRectangle& rCurrBounds( BackgroundShape::getBounds() ); + + if( rCurrBounds.getRange().equalZero() ) + { + // zero-sized shapes are effectively invisible, + // thus, we save us the rendering... + return true; + } + + // redraw all view shapes, by calling their render() method + if( o3tl::make_unsigned(::std::count_if( maViewShapes.begin(), + maViewShapes.end(), + [this]( const ViewBackgroundShapeSharedPtr& pBgShape ) + { return pBgShape->render( this->mpMtf ); } )) + != maViewShapes.size() ) + { + // at least one of the ViewBackgroundShape::render() calls did return + // false - update failed on at least one ViewLayer + return false; + } + + return true; + } + + bool BackgroundShape::isContentChanged() const + { + return false; + } + + bool BackgroundShape::isBackgroundDetached() const + { + return false; // we're not animatable + } + + + ShapeSharedPtr createBackgroundShape( + const uno::Reference< drawing::XDrawPage >& xDrawPage, + const uno::Reference< drawing::XDrawPage >& xMasterPage, + const SlideShowContext& rContext ) + { + return std::make_shared<BackgroundShape>( + xDrawPage, + xMasterPage, + rContext ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/backgroundshape.hxx b/slideshow/source/engine/shapes/backgroundshape.hxx new file mode 100644 index 000000000..3e7092498 --- /dev/null +++ b/slideshow/source/engine/shapes/backgroundshape.hxx @@ -0,0 +1,51 @@ +/* -*- 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_SHAPES_BACKGROUNDSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_BACKGROUNDSHAPE_HXX + +#include <com/sun/star/uno/Reference.hxx> + +#include <memory> + +namespace com::sun::star::drawing { class XDrawPage; } + +namespace slideshow::internal + { + class Shape; + struct SlideShowContext; + typedef ::std::shared_ptr< Shape > ShapeSharedPtr; + + /** Representation of a draw document's background shape. + + This function generates the Shape for the background + shape. Since the background shape is neither animatable + nor attributable, those more specialized derivations of + the Shape interface are not implemented here. + */ + ShapeSharedPtr createBackgroundShape( + const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage, + const css::uno::Reference< css::drawing::XDrawPage >& xMasterPage, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_BACKGROUNDSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawinglayeranimation.cxx b/slideshow/source/engine/shapes/drawinglayeranimation.cxx new file mode 100644 index 000000000..0b37071fb --- /dev/null +++ b/slideshow/source/engine/shapes/drawinglayeranimation.cxx @@ -0,0 +1,932 @@ +/* -*- 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 <tools/gen.hxx> +#include <tools/helpers.hxx> +#include <canvas/elapsedtime.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> + +#include <vcl/canvastools.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/TextAnimationKind.hpp> +#include <com/sun/star/drawing/TextAnimationDirection.hpp> + +#include <activity.hxx> +#include <wakeupevent.hxx> +#include <eventqueue.hxx> +#include "drawinglayeranimation.hxx" +#include "drawshapesubsetting.hxx" +#include "drawshape.hxx" +#include <shapeattributelayerholder.hxx> +#include <slideshowcontext.hxx> +#include <subsettableshapemanager.hxx> +#include <tools.hxx> +#include "gdimtftools.hxx" +#include <intrinsicanimationeventhandler.hxx> + +#include <vector> +#include <memory> + +using namespace com::sun::star; +using namespace ::slideshow::internal; + +namespace { + +class ScrollTextAnimNode +{ + sal_uInt32 mnDuration; // single duration + sal_uInt32 mnRepeat; // 0 -> endless + double mfStart; + double mfStop; + sal_uInt32 mnFrequency; // in ms + // forth and back change at mnRepeat%2: + bool mbAlternate; + +public: + ScrollTextAnimNode( + sal_uInt32 nDuration, sal_uInt32 nRepeat, double fStart, double fStop, + sal_uInt32 nFrequency, bool bAlternate) + : mnDuration(nDuration), + mnRepeat(nRepeat), + mfStart(fStart), + mfStop(fStop), + mnFrequency(nFrequency), + mbAlternate(bAlternate) + {} + + sal_uInt32 GetRepeat() const { return mnRepeat; } + sal_uInt32 GetFullTime() const { return mnDuration * mnRepeat; } + double GetStop() const { return mfStop; } + sal_uInt32 GetFrequency() const { return mnFrequency; } + bool DoAlternate() const { return mbAlternate; } + + double GetStateAtRelativeTime(sal_uInt32 nRelativeTime) const; +}; + +double ScrollTextAnimNode::GetStateAtRelativeTime( + sal_uInt32 nRelativeTime) const +{ + // Avoid division by zero. + if( mnDuration == 0 ) + return mfStop; + + if(mnRepeat) + { + // ending + const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration); + sal_uInt32 nFrameTime(nRelativeTime - (nRepeatCount * mnDuration)); + + if(DoAlternate() && (nRepeatCount + 1) % 2L) + nFrameTime = mnDuration - nFrameTime; + + return mfStart + ((mfStop - mfStart) * + (double(nFrameTime) / mnDuration)); + } + else + { + // endless + sal_uInt32 nFrameTime(nRelativeTime % mnDuration); + + if(DoAlternate()) + { + const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration); + + if((nRepeatCount + 1) % 2L) + nFrameTime = mnDuration - nFrameTime; + } + + return mfStart + ((mfStop - mfStart) * (double(nFrameTime) / mnDuration)); + } +} + +class ActivityImpl : public Activity +{ +public: + ActivityImpl( + SlideShowContext const& rContext, + std::shared_ptr<WakeupEvent> const& pWakeupEvent, + std::shared_ptr<DrawShape> const& pDrawShape ); + + ActivityImpl(const ActivityImpl&) = delete; + ActivityImpl& operator=(const ActivityImpl&) = delete; + + bool enableAnimations(); + + // Disposable: + virtual void dispose() override; + // Activity: + virtual double calcTimeLag() const override; + virtual bool perform() override; + virtual bool isActive() const override; + virtual void dequeued() override; + virtual void end() override; + +private: + void updateShapeAttributes( double fTime, + basegfx::B2DRectangle const& parentBounds ); + + // scroll horizontal? if sal_False, scroll is vertical. + bool ScrollHorizontal() const { + return (drawing::TextAnimationDirection_LEFT == meDirection || + drawing::TextAnimationDirection_RIGHT == meDirection); + } + + // Access to StepWidth in logical units + sal_uInt32 GetStepWidthLogic() const; + + // is the animation direction opposite? + bool DoScrollForward() const { + return (drawing::TextAnimationDirection_RIGHT == meDirection || + drawing::TextAnimationDirection_DOWN == meDirection); + } + + // do alternate text directions? + bool DoAlternate() const { return mbAlternate; } + + // do scroll in? + bool DoScrollIn() const { return mbScrollIn; } + + // Scroll helper methods + void ImpForceScrollTextAnimNodes(); + ScrollTextAnimNode* ImpGetScrollTextAnimNode( + sal_uInt32 nTime, sal_uInt32& rRelativeTime ); + sal_uInt32 ImpRegisterAgainScrollTextMixerState( + sal_uInt32 nTime); + + // calculate the MixerState value for given time + double GetMixerState(sal_uInt32 nTime); + + + SlideShowContext maContext; + std::shared_ptr<WakeupEvent> mpWakeupEvent; + std::weak_ptr<DrawShape> mpParentDrawShape; + DrawShapeSharedPtr mpDrawShape; + ShapeAttributeLayerHolder maShapeAttrLayer; + GDIMetaFileSharedPtr mpMetaFile; + IntrinsicAnimationEventHandlerSharedPtr mpListener; + canvas::tools::ElapsedTime maTimer; + double mfRotationAngle; + bool mbIsShapeAnimated; + bool mbIsDisposed; + bool mbIsActive; + drawing::TextAnimationKind meAnimKind; + + // The blink frequency in ms + sal_uInt32 mnFrequency; + + // The repeat count, init to 0L which means endless + sal_uInt32 mnRepeat; + + // Flag to decide if text will be shown when animation has ended + bool mbVisibleWhenStopped; + bool mbVisibleWhenStarted; + + // Flag decides if TextScroll alternates. Default is sal_False. + bool mbAlternate; + + // Flag to remember if this is a simple scrolling text + bool mbScrollIn; + + // The AnimationDirection + drawing::TextAnimationDirection meDirection; + + // Get width per Step. Negative means pixel, positive logical units + sal_Int32 mnStepWidth; + + // The single anim steps + std::vector< ScrollTextAnimNode > maVector; + + // the scroll rectangle + tools::Rectangle maScrollRectangleLogic; + + // the paint rectangle + tools::Rectangle maPaintRectangleLogic; +}; + + +class IntrinsicAnimationListener : public IntrinsicAnimationEventHandler +{ +public: + explicit IntrinsicAnimationListener( ActivityImpl& rActivity ) : + mrActivity( rActivity ) + {} + + IntrinsicAnimationListener(const IntrinsicAnimationListener&) = delete; + IntrinsicAnimationListener& operator=(const IntrinsicAnimationListener&) = delete; + +private: + + virtual bool enableAnimations() override { return mrActivity.enableAnimations(); } + virtual bool disableAnimations() override { mrActivity.end(); return true; } + + ActivityImpl& mrActivity; +}; + + +double ActivityImpl::GetMixerState( sal_uInt32 nTime ) +{ + if( meAnimKind == drawing::TextAnimationKind_BLINK ) + { + // from AInfoBlinkText: + double fRetval(0.0); + bool bDone(false); + const sal_uInt32 nLoopTime(2 * mnFrequency); + + if(mnRepeat) + { + const sal_uInt32 nEndTime(mnRepeat * nLoopTime); + + if(nTime >= nEndTime) + { + if(mbVisibleWhenStopped) + fRetval = 0.0; + else + fRetval = 1.0; + + bDone = true; + } + } + + if(!bDone) + { + sal_uInt32 nTimeInLoop(nTime % nLoopTime); + fRetval = double(nTimeInLoop) / nLoopTime; + } + + return fRetval; + } + else + { + // from AInfoScrollText: + double fRetval(0.0); + ImpForceScrollTextAnimNodes(); + + if(!maVector.empty()) + { + sal_uInt32 nRelativeTime; + ScrollTextAnimNode* pNode = + ImpGetScrollTextAnimNode(nTime, nRelativeTime); + + if(pNode) + { + // use node + fRetval = pNode->GetStateAtRelativeTime(nRelativeTime); + } + else + { + // end of animation, take last entry's end + fRetval = maVector[maVector.size() - 1].GetStop(); + } + } + + return fRetval; + } +} + +// Access to StepWidth in logical units +sal_uInt32 ActivityImpl::GetStepWidthLogic() const +{ + // #i69847# Assuming higher DPI + constexpr sal_uInt32 PIXEL_TO_LOGIC = 30; + + sal_uInt32 nRetval(0); + + if(mnStepWidth < 0) + { + // is in pixels, convert to logical units + nRetval = (-mnStepWidth * PIXEL_TO_LOGIC); + } + else if(mnStepWidth > 0) + { + // is in logical units + nRetval = mnStepWidth; + } + + if(0 == nRetval) + { + // step 1 pixel, canned value + + // with very high DPIs like in PDF export, this can + // still get zero. for that cases, set a default, too (taken + // from ainfoscrolltext.cxx) + nRetval = 100; + } + + return nRetval; +} + +void ActivityImpl::ImpForceScrollTextAnimNodes() +{ + if(!maVector.empty()) + return; + + // prepare values + sal_uInt32 nLoopTime; + double fZeroLogic, fOneLogic, fInitLogic, fDistanceLogic; + double fZeroLogicAlternate = 0.0, fOneLogicAlternate = 0.0; + double fZeroRelative, fOneRelative, fInitRelative; + + if(ScrollHorizontal()) + { + if(DoAlternate()) + { + if(maPaintRectangleLogic.GetWidth() > + maScrollRectangleLogic.GetWidth()) + { + fZeroLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth(); + fOneLogicAlternate = maScrollRectangleLogic.Left(); + } + else + { + fZeroLogicAlternate = maScrollRectangleLogic.Left(); + fOneLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth(); + } + } + + fZeroLogic = maScrollRectangleLogic.Left() - maPaintRectangleLogic.GetWidth(); + fOneLogic = maScrollRectangleLogic.Right(); + fInitLogic = maPaintRectangleLogic.Left(); + } + else + { + if(DoAlternate()) + { + if(maPaintRectangleLogic.GetHeight() > maScrollRectangleLogic.GetHeight()) + { + fZeroLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight(); + fOneLogicAlternate = maScrollRectangleLogic.Top(); + } + else + { + fZeroLogicAlternate = maScrollRectangleLogic.Top(); + fOneLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight(); + } + } + + fZeroLogic = maScrollRectangleLogic.Top() - maPaintRectangleLogic.GetHeight(); + fOneLogic = maScrollRectangleLogic.Bottom(); + fInitLogic = maPaintRectangleLogic.Top(); + } + + fDistanceLogic = fOneLogic - fZeroLogic; + fInitRelative = (fInitLogic - fZeroLogic) / fDistanceLogic; + + if(DoAlternate()) + { + fZeroRelative = + (fZeroLogicAlternate - fZeroLogic) / fDistanceLogic; + fOneRelative = + (fOneLogicAlternate - fZeroLogic) / fDistanceLogic; + } + else + { + fZeroRelative = 0.0; + fOneRelative = 1.0; + } + + if(mbVisibleWhenStarted) + { + double fRelativeStartValue, fRelativeEndValue,fRelativeDistance; + + if(DoScrollForward()) + { + fRelativeStartValue = fInitRelative; + fRelativeEndValue = fOneRelative; + fRelativeDistance = fRelativeEndValue - fRelativeStartValue; + } + else + { + fRelativeStartValue = fInitRelative; + fRelativeEndValue = fZeroRelative; + fRelativeDistance = fRelativeStartValue - fRelativeEndValue; + } + + const double fNumberSteps = + (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic(); + nLoopTime = FRound(fNumberSteps * mnFrequency); + + // init loop + ScrollTextAnimNode aInitNode( + nLoopTime, 1, + fRelativeStartValue, fRelativeEndValue, + mnFrequency, false); + maVector.push_back(aInitNode); + } + + // prepare main loop values + { + double fRelativeStartValue, fRelativeEndValue, fRelativeDistance; + + if(DoScrollForward()) + { + fRelativeStartValue = fZeroRelative; + fRelativeEndValue = fOneRelative; + fRelativeDistance = fRelativeEndValue - fRelativeStartValue; + } + else + { + fRelativeStartValue = fOneRelative; + fRelativeEndValue = fZeroRelative; + fRelativeDistance = fRelativeStartValue - fRelativeEndValue; + } + + const double fNumberSteps = + (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic(); + nLoopTime = FRound(fNumberSteps * mnFrequency); + + if(0 == mnRepeat) + { + if(!DoScrollIn()) + { + // endless main loop + ScrollTextAnimNode aMainNode( + nLoopTime, 0, + fRelativeStartValue, fRelativeEndValue, + mnFrequency, DoAlternate()); + maVector.push_back(aMainNode); + } + } + else + { + sal_uInt32 nNumRepeat(mnRepeat); + + if(DoAlternate() && (nNumRepeat + 1) % 2L) + nNumRepeat += 1; + + // ending main loop + ScrollTextAnimNode aMainNode( + nLoopTime, nNumRepeat, + fRelativeStartValue, fRelativeEndValue, + mnFrequency, DoAlternate()); + maVector.push_back(aMainNode); + } + } + + if(!mbVisibleWhenStopped) + return; + + double fRelativeStartValue, fRelativeEndValue, fRelativeDistance; + + if(DoScrollForward()) + { + fRelativeStartValue = fZeroRelative; + fRelativeEndValue = fInitRelative; + fRelativeDistance = fRelativeEndValue - fRelativeStartValue; + } + else + { + fRelativeStartValue = fOneRelative; + fRelativeEndValue = fInitRelative; + fRelativeDistance = fRelativeStartValue - fRelativeEndValue; + } + + const double fNumberSteps = + (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic(); + nLoopTime = FRound(fNumberSteps * mnFrequency); + + // exit loop + ScrollTextAnimNode aExitNode( + nLoopTime, 1, + fRelativeStartValue, fRelativeEndValue, mnFrequency, false); + maVector.push_back(aExitNode); +} + +ScrollTextAnimNode* ActivityImpl::ImpGetScrollTextAnimNode( + sal_uInt32 nTime, sal_uInt32& rRelativeTime ) +{ + ScrollTextAnimNode* pRetval = nullptr; + ImpForceScrollTextAnimNodes(); + + if(!maVector.empty()) + { + rRelativeTime = nTime; + + for(ScrollTextAnimNode & rNode: maVector) + { + if(!rNode.GetRepeat()) + { + // endless loop, use it + pRetval = &rNode; + } + else if(rNode.GetFullTime() > rRelativeTime) + { + // ending node + pRetval = &rNode; + } + else + { + // look at next + rRelativeTime -= rNode.GetFullTime(); + } + } + } + + return pRetval; +} + +sal_uInt32 ActivityImpl::ImpRegisterAgainScrollTextMixerState(sal_uInt32 nTime) +{ + sal_uInt32 nRetval(0); + ImpForceScrollTextAnimNodes(); + + if(!maVector.empty()) + { + sal_uInt32 nRelativeTime; + ScrollTextAnimNode* pNode = ImpGetScrollTextAnimNode(nTime, nRelativeTime); + + if(pNode) + { + // take register time + nRetval = pNode->GetFrequency(); + } + } + else + { + // #i38135# not initialized, return default + nRetval = mnFrequency; + } + + return nRetval; +} + +void ActivityImpl::updateShapeAttributes( + double fTime, basegfx::B2DRectangle const& parentBounds ) +{ + OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE ); + if( meAnimKind == drawing::TextAnimationKind_NONE ) + return; + + double const fMixerState = GetMixerState( + static_cast<sal_uInt32>(fTime * 1000.0) ); + + if( meAnimKind == drawing::TextAnimationKind_BLINK ) + { + // show/hide text: + maShapeAttrLayer.get()->setVisibility( fMixerState < 0.5 ); + } + else if(mpMetaFile) // scroll mode: + { + + // keep care: the below code is highly sensible to changes... + + + // rectangle of the pure text: + double const fPaintWidth = maPaintRectangleLogic.GetWidth(); + double const fPaintHeight = maPaintRectangleLogic.GetHeight(); + // rectangle where the scrolling takes place (-> clipping): + double const fScrollWidth = maScrollRectangleLogic.GetWidth(); + double const fScrollHeight = maScrollRectangleLogic.GetHeight(); + + basegfx::B2DPoint pos, clipPos; + + if(ScrollHorizontal()) + { + double const fOneEquiv( fScrollWidth ); + double const fZeroEquiv( -fPaintWidth ); + + pos.setX( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) ); + + clipPos.setX( -pos.getX() ); + clipPos.setY( -pos.getY() ); + + // #i69844# Compensation for text-wider-than-shape case + if( fPaintWidth > fScrollWidth ) + pos.setX( pos.getX() + (fPaintWidth-fScrollWidth) / 2.0 ); + } + else + { + // scroll vertical: + double const fOneEquiv( fScrollHeight ); + double const fZeroEquiv( -fPaintHeight ); + + pos.setY( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) ); + + clipPos.setX( -pos.getX() ); + clipPos.setY( -pos.getY() ); + + // #i69844# Compensation for text-higher-than-shape case + if( fPaintHeight > fScrollHeight ) + pos.setY( pos.getY() + (fPaintHeight-fScrollHeight) / 2.0 ); + } + + basegfx::B2DPolygon clipPoly( + basegfx::utils::createPolygonFromRect( + basegfx::B2DRectangle( clipPos.getX(), + clipPos.getY(), + clipPos.getX() + fScrollWidth, + clipPos.getY() + fScrollHeight ) ) ); + + if( !::basegfx::fTools::equalZero( mfRotationAngle )) + { + maShapeAttrLayer.get()->setRotationAngle( mfRotationAngle ); + double const fRotate = basegfx::deg2rad(mfRotationAngle); + basegfx::B2DHomMatrix aTransform; + // position: + aTransform.rotate( fRotate ); + pos *= aTransform; + } + + pos += parentBounds.getCenter(); + maShapeAttrLayer.get()->setPosition( pos ); + maShapeAttrLayer.get()->setClip( basegfx::B2DPolyPolygon(clipPoly) ); + } +} + +bool ActivityImpl::perform() +{ + if( !isActive() ) + return false; + + ENSURE_OR_RETURN_FALSE( + mpDrawShape, + "ActivityImpl::perform(): still active, but NULL draw shape" ); + + DrawShapeSharedPtr const pParentDrawShape( mpParentDrawShape ); + if( !pParentDrawShape ) + return false; // parent has vanished + + if( pParentDrawShape->isVisible() ) + { + if( !mbIsShapeAnimated ) + { + mpDrawShape->setVisibility(true); // shape may be initially hidden + maContext.mpSubsettableShapeManager->enterAnimationMode( mpDrawShape ); + maTimer.reset(); + mbIsShapeAnimated = true; + } + // update attributes related to current time: + basegfx::B2DRectangle const parentBounds( + pParentDrawShape->getBounds() ); + + const double nCurrTime( maTimer.getElapsedTime() ); + updateShapeAttributes( nCurrTime, parentBounds ); + + const sal_uInt32 nFrequency( + ImpRegisterAgainScrollTextMixerState( + static_cast<sal_uInt32>(nCurrTime * 1000.0)) ); + + if(nFrequency) + { + mpWakeupEvent->start(); + mpWakeupEvent->setNextTimeout( + std::max(0.1,nFrequency/1000.0) ); + maContext.mrEventQueue.addEvent( mpWakeupEvent ); + + if( mpDrawShape->isContentChanged() ) + maContext.mpSubsettableShapeManager->notifyShapeUpdate( mpDrawShape ); + } + // else: finished, not need to wake up again. + } + else + { + // busy-wait, until parent shape gets visible + mpWakeupEvent->start(); + mpWakeupEvent->setNextTimeout( 2.0 ); + } + + // don't reinsert, WakeupEvent will perform that after the given timeout: + return false; +} + +ActivityImpl::ActivityImpl( + SlideShowContext const& rContext, + std::shared_ptr<WakeupEvent> const& pWakeupEvent, + std::shared_ptr<DrawShape> const& pParentDrawShape ) + : maContext(rContext), + mpWakeupEvent(pWakeupEvent), + mpParentDrawShape(pParentDrawShape), + mpListener( std::make_shared<IntrinsicAnimationListener>(*this) ), + maTimer(rContext.mrEventQueue.getTimer()), + mfRotationAngle(0.0), + mbIsShapeAnimated(false), + mbIsDisposed(false), + mbIsActive(true), + meAnimKind(drawing::TextAnimationKind_NONE), + mbVisibleWhenStopped(false), + mbVisibleWhenStarted(false), + mnStepWidth(0) +{ + // get doctreenode: + sal_Int32 const nNodes = pParentDrawShape->getNumberOfTreeNodes( + DocTreeNode::NodeType::LogicalParagraph ); + + DocTreeNode scrollTextNode( + pParentDrawShape->getTreeNode( + 0, DocTreeNode::NodeType::LogicalParagraph )); + // xxx todo: remove this hack + if( nNodes > 1 ) + scrollTextNode.setEndIndex( + pParentDrawShape->getTreeNode( + nNodes - 1, + DocTreeNode::NodeType::LogicalParagraph ).getEndIndex()); + + // TODO(Q3): Doing this manually, instead of using + // ShapeSubset. This is because of lifetime issues (ShapeSubset + // generates circular references to parent shape) + mpDrawShape = std::dynamic_pointer_cast<DrawShape>( + maContext.mpSubsettableShapeManager->getSubsetShape( + pParentDrawShape, + scrollTextNode )); + + mpMetaFile = mpDrawShape->forceScrollTextMetaFile(); + + // make scroll text invisible for slide transition bitmaps + mpDrawShape->setVisibility(false); + + basegfx::B2DRectangle aScrollRect, aPaintRect; + ENSURE_OR_THROW( getRectanglesFromScrollMtf( aScrollRect, + aPaintRect, + mpMetaFile ), + "ActivityImpl::ActivityImpl(): Could not extract " + "scroll anim rectangles from mtf" ); + + maScrollRectangleLogic = vcl::unotools::rectangleFromB2DRectangle( + aScrollRect ); + maPaintRectangleLogic = vcl::unotools::rectangleFromB2DRectangle( + aPaintRect ); + + maShapeAttrLayer.createAttributeLayer(mpDrawShape); + + uno::Reference<drawing::XShape> const xShape( mpDrawShape->getXShape() ); + uno::Reference<beans::XPropertySet> const xProps( xShape, uno::UNO_QUERY_THROW ); + + getPropertyValue( meAnimKind, xProps, "TextAnimationKind" ); + OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE ); + mbAlternate = (meAnimKind == drawing::TextAnimationKind_ALTERNATE); + mbScrollIn = (meAnimKind == drawing::TextAnimationKind_SLIDE); + + // adopted from in AInfoBlinkText::ImplInit(): + sal_Int16 nRepeat(0); + getPropertyValue( nRepeat, xProps, "TextAnimationCount" ); + mnRepeat = nRepeat; + + if(mbAlternate) + { + // force visible when started for scroll-forth-and-back, because + // slide has been coming in with visible text in the middle: + mbVisibleWhenStarted = true; + } + else + { + getPropertyValue( mbVisibleWhenStarted, xProps, + "TextAnimationStartInside" ); + } + + // set visible when stopped + getPropertyValue( mbVisibleWhenStopped, xProps, + "TextAnimatiogonStopInside" ); + // rotation: + getPropertyValue( mfRotationAngle, xProps, + "RotateAngle" ); + mfRotationAngle /= -100.0; // (switching direction) + + // set frequency + sal_Int16 nDelay(0); + getPropertyValue( nDelay, xProps, "TextAnimationDelay" ); + // set delay if not automatic + mnFrequency = (nDelay ? nDelay : + // default: + meAnimKind == drawing::TextAnimationKind_BLINK + ? 250 : 50 ); + + // adopted from in AInfoScrollText::ImplInit(): + + // If it is a simple m_bScrollIn, reset some parameters + if( DoScrollIn() ) + { + // most parameters are set correctly from the dialog logic, but + // eg VisibleWhenStopped is grayed out and needs to be corrected here. + mbVisibleWhenStopped = true; + mbVisibleWhenStarted = false; + mnRepeat = 0; + } + + // Get animation direction + getPropertyValue( meDirection, xProps, "TextAnimationDirection" ); + + // Get step width. Negative means pixel, positive logical units + getPropertyValue( mnStepWidth, xProps, "TextAnimationAmount" ); + + maContext.mpSubsettableShapeManager->addIntrinsicAnimationHandler( + mpListener ); +} + +bool ActivityImpl::enableAnimations() +{ + mbIsActive = true; + return maContext.mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) ); +} + +void ActivityImpl::dispose() +{ + if( mbIsDisposed ) + return; + + end(); + + // only remove subset here, since end() is called on slide end + // (and we must not spoil the slide preview bitmap with scroll + // text) + maShapeAttrLayer.reset(); + if( mpDrawShape ) + { + // TODO(Q3): Doing this manually, instead of using + // ShapeSubset. This is because of lifetime issues + // (ShapeSubset generates circular references to parent + // shape) + DrawShapeSharedPtr pParent( mpParentDrawShape.lock() ); + if( pParent ) + maContext.mpSubsettableShapeManager->revokeSubset( + pParent, + mpDrawShape ); + } + + mpMetaFile.reset(); + mpDrawShape.reset(); + mpParentDrawShape.reset(); + mpWakeupEvent.reset(); + maContext.dispose(); + mbIsDisposed = true; + + maContext.mpSubsettableShapeManager->removeIntrinsicAnimationHandler( + mpListener ); +} + +double ActivityImpl::calcTimeLag() const +{ + return 0.0; +} + +bool ActivityImpl::isActive() const +{ + return mbIsActive; +} + +void ActivityImpl::dequeued() +{ + // not used here +} + +void ActivityImpl::end() +{ + // not used here + mbIsActive = false; + + if( mbIsShapeAnimated ) + { + maContext.mpSubsettableShapeManager->leaveAnimationMode( mpDrawShape ); + mbIsShapeAnimated = false; + } +} + +} // anon namespace + +namespace slideshow::internal { + +std::shared_ptr<Activity> createDrawingLayerAnimActivity( + SlideShowContext const& rContext, + std::shared_ptr<DrawShape> const& pDrawShape ) +{ + std::shared_ptr<Activity> pActivity; + + try + { + auto const pWakeupEvent = std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(), + rContext.mrActivitiesQueue ); + pActivity = std::make_shared<ActivityImpl>( rContext, pWakeupEvent, pDrawShape ); + pWakeupEvent->setActivity( pActivity ); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + // translate any error into empty factory product. + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + + return pActivity; +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawinglayeranimation.hxx b/slideshow/source/engine/shapes/drawinglayeranimation.hxx new file mode 100644 index 000000000..5a143b087 --- /dev/null +++ b/slideshow/source/engine/shapes/drawinglayeranimation.hxx @@ -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 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX + +#include <sal/config.h> +#include <memory> + +namespace slideshow::internal { + +class Activity; +struct SlideShowContext; +class DrawShape; + +std::shared_ptr<Activity> createDrawingLayerAnimActivity( + SlideShowContext const& rContext, + std::shared_ptr<DrawShape> const& pDrawShape ); + +} // namespace presentation::internal + +#endif // ! defined INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawshape.cxx b/slideshow/source/engine/shapes/drawshape.cxx new file mode 100644 index 000000000..000ffd262 --- /dev/null +++ b/slideshow/source/engine/shapes/drawshape.cxx @@ -0,0 +1,1228 @@ +/* -*- 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 <sal/log.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <o3tl/safeint.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> + +#include <basegfx/numeric/ftools.hxx> + +#include <com/sun/star/drawing/TextAnimationKind.hpp> + +#include <comphelper/scopeguard.hxx> + +#include <algorithm> +#include <iterator> +#include <functional> + +#include "drawshapesubsetting.hxx" +#include "drawshape.hxx" +#include <eventqueue.hxx> +#include <wakeupevent.hxx> +#include <subsettableshapemanager.hxx> +#include "intrinsicanimationactivity.hxx" +#include <tools.hxx> +#include "gdimtftools.hxx" +#include "drawinglayeranimation.hxx" + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + + + // Private methods + + + GDIMetaFileSharedPtr const & DrawShape::forceScrollTextMetaFile() + { + if ((mnCurrMtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != MTF_LOAD_SCROLL_TEXT_MTF) + { + // reload with added flags: + mnCurrMtfLoadFlags |= MTF_LOAD_SCROLL_TEXT_MTF; + mpCurrMtf = getMetaFile(uno::Reference<lang::XComponent>(mxShape, uno::UNO_QUERY), + mxPage, mnCurrMtfLoadFlags, + mxComponentContext); + + if (!mpCurrMtf) + mpCurrMtf = std::make_shared<GDIMetaFile>(); + + // TODO(F1): Currently, the scroll metafile will + // never contain any verbose text comments. Thus, + // can only display the full mtf content, no + // subsets. + maSubsetting.reset( mpCurrMtf ); + + // adapt maBounds. the requested scroll text metafile + // will typically have dimension different from the + // actual shape + ::basegfx::B2DRectangle aScrollRect, aPaintRect; + ENSURE_OR_THROW( getRectanglesFromScrollMtf( aScrollRect, + aPaintRect, + mpCurrMtf ), + "DrawShape::forceScrollTextMetaFile(): Could " + "not extract scroll anim rectangles from mtf" ); + + // take the larger one of the two rectangles (that + // should be the bound rect of the retrieved + // metafile) + if( aScrollRect.isInside( aPaintRect ) ) + maBounds = aScrollRect; + else + maBounds = aPaintRect; + } + return mpCurrMtf; + } + + void DrawShape::updateStateIds() const + { + // Update the states, we've just redrawn or created a new + // attribute layer. + if( mpAttributeLayer ) + { + mnAttributeTransformationState = mpAttributeLayer->getTransformationState(); + mnAttributeClipState = mpAttributeLayer->getClipState(); + mnAttributeAlphaState = mpAttributeLayer->getAlphaState(); + mnAttributePositionState = mpAttributeLayer->getPositionState(); + mnAttributeContentState = mpAttributeLayer->getContentState(); + mnAttributeVisibilityState = mpAttributeLayer->getVisibilityState(); + } + } + + ViewShape::RenderArgs DrawShape::getViewRenderArgs() const + { + return ViewShape::RenderArgs( + maBounds, + getUpdateArea(), + getBounds(), + getActualUnitShapeBounds(), + mpAttributeLayer, + maSubsetting.getActiveSubsets(), + mnPriority); + } + + bool DrawShape::implRender( UpdateFlags nUpdateFlags ) const + { + SAL_INFO( "slideshow", "::presentation::internal::DrawShape::implRender()" ); + SAL_INFO( "slideshow", "::presentation::internal::DrawShape: 0x" << std::hex << this ); + + // will perform the update now, clear update-enforcing + // flags + mbForceUpdate = false; + mbAttributeLayerRevoked = false; + + ENSURE_OR_RETURN_FALSE( !maViewShapes.empty(), + "DrawShape::implRender(): render called on DrawShape without views" ); + + if( maBounds.isEmpty() ) + { + // zero-sized shapes are effectively invisible, + // thus, we save us the rendering... + return true; + } + + // redraw all view shapes, by calling their update() method + ViewShape::RenderArgs renderArgs( getViewRenderArgs() ); + bool bVisible = isVisible(); + if( o3tl::make_unsigned(::std::count_if( maViewShapes.begin(), + maViewShapes.end(), + [this, &bVisible, &renderArgs, &nUpdateFlags] + ( const ViewShapeSharedPtr& pShape ) + { return pShape->update( this->mpCurrMtf, + renderArgs, + nUpdateFlags, + bVisible ); } )) + != maViewShapes.size() ) + { + // at least one of the ViewShape::update() calls did return + // false - update failed on at least one ViewLayer + return false; + } + + // successfully redrawn - update state IDs to detect next changes + updateStateIds(); + + return true; + } + + UpdateFlags DrawShape::getUpdateFlags() const + { + // default: update nothing, unless ShapeAttributeStack + // tells us below, or if the attribute layer was revoked + UpdateFlags nUpdateFlags(UpdateFlags::NONE); + + // possibly the whole shape content changed + if( mbAttributeLayerRevoked ) + nUpdateFlags = UpdateFlags::Content; + + + // determine what has to be updated + + + // do we have an attribute layer? + if( mpAttributeLayer ) + { + // Prevent nUpdateFlags to be modified when the shape is not + // visible, except when it just was hidden. + if (mpAttributeLayer->getVisibility() + || mpAttributeLayer->getVisibilityState() != mnAttributeVisibilityState ) + { + if (mpAttributeLayer->getVisibilityState() != mnAttributeVisibilityState ) + { + // Change of the visibility state is mapped to + // content change because when the visibility + // changes then usually a sprite is shown or hidden + // and the background under has to be painted once. + nUpdateFlags |= UpdateFlags::Content; + } + + // TODO(P1): This can be done without conditional branching. + // See HAKMEM. + if( mpAttributeLayer->getPositionState() != mnAttributePositionState ) + { + nUpdateFlags |= UpdateFlags::Position; + } + if( mpAttributeLayer->getAlphaState() != mnAttributeAlphaState ) + { + nUpdateFlags |= UpdateFlags::Alpha; + } + if( mpAttributeLayer->getClipState() != mnAttributeClipState ) + { + nUpdateFlags |= UpdateFlags::Clip; + } + if( mpAttributeLayer->getTransformationState() != mnAttributeTransformationState ) + { + nUpdateFlags |= UpdateFlags::Transformation; + } + if( mpAttributeLayer->getContentState() != mnAttributeContentState ) + { + nUpdateFlags |= UpdateFlags::Content; + } + } + } + + return nUpdateFlags; + } + + ::basegfx::B2DRectangle DrawShape::getActualUnitShapeBounds() const + { + ENSURE_OR_THROW( !maViewShapes.empty(), + "DrawShape::getActualUnitShapeBounds(): called on DrawShape without views" ); + + const VectorOfDocTreeNodes& rSubsets( + maSubsetting.getActiveSubsets() ); + + const ::basegfx::B2DRectangle aDefaultBounds( 0.0,0.0,1.0,1.0 ); + + // perform the cheapest check first + if( rSubsets.empty() ) + { + // if subset contains the whole shape, no need to call + // the somewhat expensive bound calculation, since as + // long as the subset is empty, this branch will be + // taken. + return aDefaultBounds; + } + else + { + OSL_ENSURE( rSubsets.size() != 1 || + !rSubsets.front().isEmpty(), + "DrawShape::getActualUnitShapeBounds() expects a " + "_non-empty_ subset vector for a subsetted shape!" ); + + // are the cached bounds still valid? + if( !maCurrentShapeUnitBounds ) + { + // no, (re)generate them + // ===================== + + // setup cached values to defaults (might fail to + // retrieve true bounds below) + maCurrentShapeUnitBounds = aDefaultBounds; + + // TODO(P2): the subset of the master shape (that from + // which the subsets are subtracted) changes + // relatively often (every time a subset shape is + // added or removed). Maybe we should exclude it here, + // always assuming full bounds? + + ::cppcanvas::CanvasSharedPtr pDestinationCanvas( + maViewShapes.front()->getViewLayer()->getCanvas() ); + + // TODO(Q2): Although this _is_ currently + // view-agnostic, it might not stay like + // that. Maybe this method should again be moved + // to the ViewShape + ::cppcanvas::RendererSharedPtr pRenderer( + maViewShapes.front()->getRenderer( + pDestinationCanvas, mpCurrMtf, mpAttributeLayer ) ); + + // If we cannot not prefetch, be defensive and assume + // full shape size + if( pRenderer ) + { + // temporarily, switch total transformation to identity + // (need the bounds in the [0,1]x[0,1] unit coordinate + // system. + ::basegfx::B2DHomMatrix aEmptyTransformation; + + ::basegfx::B2DHomMatrix aOldTransform( pDestinationCanvas->getTransformation() ); + pDestinationCanvas->setTransformation( aEmptyTransformation ); + pRenderer->setTransformation( aEmptyTransformation ); + + // restore old transformation when leaving the scope + const ::comphelper::ScopeGuard aGuard( + [&pDestinationCanvas, &aOldTransform]() + { return pDestinationCanvas->setTransformation( aOldTransform ); } ); + + + // retrieve bounds for subset of whole metafile + + + ::basegfx::B2DRange aTotalBounds; + + // cannot use ::boost::bind, ::basegfx::B2DRange::expand() + // is overloaded. + for( const auto& rDocTreeNode : rSubsets ) + aTotalBounds.expand( pRenderer->getSubsetArea( + rDocTreeNode.getStartIndex(), + rDocTreeNode.getEndIndex() ) ); + + OSL_ENSURE( aTotalBounds.getMinX() >= -0.1 && + aTotalBounds.getMinY() >= -0.1 && + aTotalBounds.getMaxX() <= 1.1 && + aTotalBounds.getMaxY() <= 1.1, + "DrawShape::getActualUnitShapeBounds(): bounds noticeably larger than original shape - clipping!" ); + + // really make sure no shape appears larger than its + // original bounds (there _are_ some pathologic cases, + // especially when imported from PPT, that have + // e.g. obscenely large polygon bounds) + aTotalBounds.intersect( + ::basegfx::B2DRange( 0.0, 0.0, + 1.0, 1.0 )); + + maCurrentShapeUnitBounds = aTotalBounds; + } + } + + return *maCurrentShapeUnitBounds; + } + } + + DrawShape::DrawShape( const uno::Reference< drawing::XShape >& xShape, + const uno::Reference< drawing::XDrawPage >& xContainingPage, + double nPrio, + bool bForeignSource, + const SlideShowContext& rContext ) : + mxShape( xShape ), + mxPage( xContainingPage ), + maAnimationFrames(), // empty, we don't have no intrinsic animation + mnCurrFrame(0), + mpCurrMtf(), + mnCurrMtfLoadFlags( bForeignSource + ? MTF_LOAD_FOREIGN_SOURCE : MTF_LOAD_NONE ), + maCurrentShapeUnitBounds(), + mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ), + maBounds( getAPIShapeBounds( xShape ) ), + mpAttributeLayer(), + mpIntrinsicAnimationActivity(), + mnAttributeTransformationState(0), + mnAttributeClipState(0), + mnAttributeAlphaState(0), + mnAttributePositionState(0), + mnAttributeContentState(0), + mnAttributeVisibilityState(0), + maViewShapes(), + mxComponentContext( rContext.mxComponentContext ), + maHyperlinkIndices(), + maHyperlinkRegions(), + maSubsetting(), + mnIsAnimatedCount(0), + mnAnimationLoopCount(0), + mbIsVisible( true ), + mbForceUpdate( false ), + mbAttributeLayerRevoked( false ), + mbDrawingLayerAnim( false ), + mbContainsPageField( false ) + { + ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" ); + ENSURE_OR_THROW( mxPage.is(), "DrawShape::DrawShape(): Invalid containing page" ); + + // check for drawing layer animations: + drawing::TextAnimationKind eKind = drawing::TextAnimationKind_NONE; + uno::Reference<beans::XPropertySet> xPropSet( mxShape, + uno::UNO_QUERY ); + if( xPropSet.is() ) + getPropertyValue( eKind, xPropSet, + "TextAnimationKind" ); + mbDrawingLayerAnim = (eKind != drawing::TextAnimationKind_NONE); + + // must NOT be called from within initializer list, uses + // state from mnCurrMtfLoadFlags! + mpCurrMtf = getMetaFile(uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY), + xContainingPage, mnCurrMtfLoadFlags, + mxComponentContext ); + if (!mpCurrMtf) + mpCurrMtf = std::make_shared<GDIMetaFile>(); + + maSubsetting.reset( mpCurrMtf ); + + prepareHyperlinkIndices(); + + if(mbContainsPageField && mpCurrMtf && !maBounds.isEmpty()) + { + // tdf#150402 Use mbContainsPageField that gets set in prepareHyperlinkIndices + // which has to be run anyways, so this will cause no harm in execution speed. + // It lets us detect the potential error case that a PageField is contained in + // the Text of the Shape. That is a hint that maBounds contains the wrong Range + // and needs to be corrected. The correct size is in the PrefSize of the metafile. + // For more backgrund information please refer to tdf#150402, Comment 16. + const double fWidthDiff(fabs(mpCurrMtf->GetPrefSize().Width() - maBounds.getWidth())); + const double fHeightDiff(fabs(mpCurrMtf->GetPrefSize().Height() - maBounds.getHeight())); + + if(fWidthDiff > 1.0 || fHeightDiff > 1.0) + { + maBounds = basegfx::B2DRange( + maBounds.getMinX(), maBounds.getMinY(), + maBounds.getMinX() + mpCurrMtf->GetPrefSize().Width(), + maBounds.getMinY() + mpCurrMtf->GetPrefSize().Height()); + } + } + } + + DrawShape::DrawShape( const uno::Reference< drawing::XShape >& xShape, + const uno::Reference< drawing::XDrawPage >& xContainingPage, + double nPrio, + const Graphic& rGraphic, + const SlideShowContext& rContext ) : + mxShape( xShape ), + mxPage( xContainingPage ), + maAnimationFrames(), + mnCurrFrame(0), + mpCurrMtf(), + mnCurrMtfLoadFlags( MTF_LOAD_NONE ), + maCurrentShapeUnitBounds(), + mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ), + maBounds( getAPIShapeBounds( xShape ) ), + mpAttributeLayer(), + mpIntrinsicAnimationActivity(), + mnAttributeTransformationState(0), + mnAttributeClipState(0), + mnAttributeAlphaState(0), + mnAttributePositionState(0), + mnAttributeContentState(0), + mnAttributeVisibilityState(0), + maViewShapes(), + mxComponentContext( rContext.mxComponentContext ), + maHyperlinkIndices(), + maHyperlinkRegions(), + maSubsetting(), + mnIsAnimatedCount(0), + mnAnimationLoopCount(0), + mbIsVisible( true ), + mbForceUpdate( false ), + mbAttributeLayerRevoked( false ), + mbDrawingLayerAnim( false ), + mbContainsPageField( false ) + { + ENSURE_OR_THROW( rGraphic.IsAnimated(), + "DrawShape::DrawShape(): Graphic is no animation" ); + + getAnimationFromGraphic( maAnimationFrames, + mnAnimationLoopCount, + rGraphic ); + + ENSURE_OR_THROW( !maAnimationFrames.empty() && + maAnimationFrames.front().mpMtf, + "DrawShape::DrawShape(): " ); + mpCurrMtf = maAnimationFrames.front().mpMtf; + + ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" ); + ENSURE_OR_THROW( mxPage.is(), "DrawShape::DrawShape(): Invalid containing page" ); + ENSURE_OR_THROW( mpCurrMtf, "DrawShape::DrawShape(): Invalid metafile" ); + } + + DrawShape::DrawShape( const DrawShape& rSrc, + const DocTreeNode& rTreeNode, + double nPrio ) : + mxShape( rSrc.mxShape ), + mxPage( rSrc.mxPage ), + maAnimationFrames(), // don't copy animations for subsets, + // only the current frame! + mnCurrFrame(0), + mpCurrMtf( rSrc.mpCurrMtf ), + mnCurrMtfLoadFlags( rSrc.mnCurrMtfLoadFlags ), + maCurrentShapeUnitBounds(), + mnPriority( nPrio ), + maBounds( rSrc.maBounds ), + mpAttributeLayer(), + mpIntrinsicAnimationActivity(), + mnAttributeTransformationState(0), + mnAttributeClipState(0), + mnAttributeAlphaState(0), + mnAttributePositionState(0), + mnAttributeContentState(0), + mnAttributeVisibilityState(0), + maViewShapes(), + mxComponentContext( rSrc.mxComponentContext ), + maHyperlinkIndices(), + maHyperlinkRegions(), + maSubsetting( rTreeNode, mpCurrMtf ), + mnIsAnimatedCount(0), + mnAnimationLoopCount(0), + mbIsVisible( rSrc.mbIsVisible ), + mbForceUpdate( false ), + mbAttributeLayerRevoked( false ), + mbDrawingLayerAnim( false ), + mbContainsPageField( false ) + { + ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" ); + ENSURE_OR_THROW( mpCurrMtf, "DrawShape::DrawShape(): Invalid metafile" ); + + // xxx todo: currently not implemented for subsetted shapes; + // would mean modifying set of hyperlink regions when + // subsetting text portions. N.B.: there's already an + // issue for this #i72828# + } + + + // Public methods + + + DrawShapeSharedPtr DrawShape::create( + const uno::Reference< drawing::XShape >& xShape, + const uno::Reference< drawing::XDrawPage >& xContainingPage, + double nPrio, + bool bForeignSource, + const SlideShowContext& rContext ) + { + DrawShapeSharedPtr pShape( new DrawShape(xShape, + xContainingPage, + nPrio, + bForeignSource, + rContext) ); + + if( pShape->hasIntrinsicAnimation() ) + { + OSL_ASSERT( pShape->maAnimationFrames.empty() ); + if( pShape->getNumberOfTreeNodes( + DocTreeNode::NodeType::LogicalParagraph) > 0 ) + { + pShape->mpIntrinsicAnimationActivity = + createDrawingLayerAnimActivity( + rContext, + pShape); + } + } + + if( pShape->hasHyperlinks() ) + rContext.mpSubsettableShapeManager->addHyperlinkArea( pShape ); + + return pShape; + } + + DrawShapeSharedPtr DrawShape::create( + const uno::Reference< drawing::XShape >& xShape, + const uno::Reference< drawing::XDrawPage >& xContainingPage, + double nPrio, + const Graphic& rGraphic, + const SlideShowContext& rContext ) + { + DrawShapeSharedPtr pShape( new DrawShape(xShape, + xContainingPage, + nPrio, + rGraphic, + rContext) ); + + if( pShape->hasIntrinsicAnimation() ) + { + OSL_ASSERT( !pShape->maAnimationFrames.empty() ); + + std::vector<double> aTimeout; + std::transform( + pShape->maAnimationFrames.begin(), + pShape->maAnimationFrames.end(), + std::back_insert_iterator< std::vector<double> >( aTimeout ), + std::mem_fn(&MtfAnimationFrame::getDuration) ); + + WakeupEventSharedPtr pWakeupEvent = + std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(), + rContext.mrActivitiesQueue ); + + ActivitySharedPtr pActivity = + createIntrinsicAnimationActivity( + rContext, + pShape, + pWakeupEvent, + std::move(aTimeout), + pShape->mnAnimationLoopCount); + + pWakeupEvent->setActivity( pActivity ); + pShape->mpIntrinsicAnimationActivity = pActivity; + } + + OSL_ENSURE( !pShape->hasHyperlinks(), + "DrawShape::create(): graphic-only shapes must not have hyperlinks!" ); + + return pShape; + } + + DrawShape::~DrawShape() + { + try + { + // dispose intrinsic animation activity, else, it will + // linger forever + ActivitySharedPtr pActivity( mpIntrinsicAnimationActivity.lock() ); + if( pActivity ) + pActivity->dispose(); + } + catch (uno::Exception const &) + { + DBG_UNHANDLED_EXCEPTION("slideshow"); + } + } + + uno::Reference< drawing::XShape > DrawShape::getXShape() const + { + return mxShape; + } + + void DrawShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) + { + // already added? + if( ::std::any_of( maViewShapes.begin(), + maViewShapes.end(), + [&rNewLayer] + ( const ViewShapeSharedPtr& pShape ) + { return rNewLayer == pShape->getViewLayer(); } ) ) + { + // yes, nothing to do + return; + } + + ViewShapeSharedPtr pNewShape = std::make_shared<ViewShape>( rNewLayer ); + + maViewShapes.push_back( pNewShape ); + + // pass on animation state + if( mnIsAnimatedCount ) + { + for( int i=0; i<mnIsAnimatedCount; ++i ) + pNewShape->enterAnimationMode(); + } + + // render the Shape on the newly added ViewLayer + if( bRedrawLayer ) + { + pNewShape->update( mpCurrMtf, + getViewRenderArgs(), + UpdateFlags::Force, + isVisible() ); + } + } + + bool DrawShape::removeViewLayer( const ViewLayerSharedPtr& rLayer ) + { + const ViewShapeVector::iterator aEnd( maViewShapes.end() ); + + OSL_ENSURE( ::std::count_if(maViewShapes.begin(), + aEnd, + [&rLayer] + ( const ViewShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) < 2, + "DrawShape::removeViewLayer(): Duplicate ViewLayer entries!" ); + + ViewShapeVector::iterator aIter; + + if( (aIter=::std::remove_if( maViewShapes.begin(), + aEnd, + [&rLayer] + ( const ViewShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) ) == aEnd ) + { + // view layer seemingly was not added, failed + return false; + } + + // actually erase from container + maViewShapes.erase( aIter, aEnd ); + + return true; + } + + void DrawShape::clearAllViewLayers() + { + maViewShapes.clear(); + } + + bool DrawShape::update() const + { + if( mbForceUpdate ) + { + return render(); + } + else + { + return implRender( getUpdateFlags() ); + } + } + + bool DrawShape::render() const + { + // force redraw. Have to also pass on the update flags, + // because e.g. content update (regeneration of the + // metafile renderer) is normally not performed. A simple + // UpdateFlags::Force would only paint the metafile in its + // old state. + return implRender( UpdateFlags::Force | getUpdateFlags() ); + } + + bool DrawShape::isContentChanged() const + { + return mbForceUpdate || + getUpdateFlags() != UpdateFlags::NONE; + } + + + ::basegfx::B2DRectangle DrawShape::getBounds() const + { + // little optimization: for non-modified shapes, we don't + // create an ShapeAttributeStack, and therefore also don't + // have to check it. + return getShapePosSize( maBounds, + mpAttributeLayer ); + } + + ::basegfx::B2DRectangle DrawShape::getDomBounds() const + { + return maBounds; + } + + ::basegfx::B2DRectangle DrawShape::getUpdateArea() const + { + ::basegfx::B2DRectangle aBounds; + + // an already empty shape bound need no further + // treatment. In fact, any changes applied below would + // actually remove the special empty state, thus, don't + // change! + if( !maBounds.isEmpty() ) + { + basegfx::B2DRectangle aUnitBounds(0.0,0.0,1.0,1.0); + + if( !maViewShapes.empty() ) + aUnitBounds = getActualUnitShapeBounds(); + + if( !aUnitBounds.isEmpty() ) + { + if( mpAttributeLayer ) + { + // calc actual shape area (in user coordinate + // space) from the transformation as given by the + // shape attribute layer + aBounds = getShapeUpdateArea( aUnitBounds, + getShapeTransformation( getBounds(), + mpAttributeLayer ), + mpAttributeLayer ); + } + else + { + // no attribute layer, thus, the true shape bounds + // can be directly derived from the XShape bound + // attribute + aBounds = getShapeUpdateArea( aUnitBounds, + maBounds ); + } + + if( !maViewShapes.empty() ) + { + // determine border needed for antialiasing the shape + ::basegfx::B2DSize aAABorder(0.0,0.0); + + // for every view, get AA border and 'expand' aAABorder + // appropriately. + for( const auto& rViewShape : maViewShapes ) + { + const ::basegfx::B2DSize rShapeBorder( rViewShape->getAntialiasingBorder() ); + + aAABorder.setX( ::std::max( + rShapeBorder.getX(), + aAABorder.getX() ) ); + aAABorder.setY( ::std::max( + rShapeBorder.getY(), + aAABorder.getY() ) ); + } + + // add calculated AA border to aBounds + aBounds = ::basegfx::B2DRectangle( aBounds.getMinX() - aAABorder.getX(), + aBounds.getMinY() - aAABorder.getY(), + aBounds.getMaxX() + aAABorder.getX(), + aBounds.getMaxY() + aAABorder.getY() ); + } + } + } + + return aBounds; + } + + bool DrawShape::isVisible() const + { + bool bIsVisible( mbIsVisible ); + + if( mpAttributeLayer ) + { + // check whether visibility and alpha are not default + // (mpAttributeLayer->isVisibilityValid() returns true + // then): bVisible becomes true, if shape visibility + // is on and alpha is not 0.0 (fully transparent) + if( mpAttributeLayer->isVisibilityValid() ) + bIsVisible = mpAttributeLayer->getVisibility(); + + // only touch bIsVisible, if the shape is still + // visible - if getVisibility already made us + // invisible, no alpha value will make us appear + // again. + if( bIsVisible && mpAttributeLayer->isAlphaValid() ) + bIsVisible = !::basegfx::fTools::equalZero( mpAttributeLayer->getAlpha() ); + } + + return bIsVisible; + } + + double DrawShape::getPriority() const + { + return mnPriority; + } + + bool DrawShape::isBackgroundDetached() const + { + return mnIsAnimatedCount > 0; + } + + bool DrawShape::hasIntrinsicAnimation() const + { + return (!maAnimationFrames.empty() || mbDrawingLayerAnim); + } + + void DrawShape::setIntrinsicAnimationFrame( ::std::size_t nCurrFrame ) + { + ENSURE_OR_RETURN_VOID( nCurrFrame < maAnimationFrames.size(), + "DrawShape::setIntrinsicAnimationFrame(): frame index out of bounds" ); + + if( mnCurrFrame != nCurrFrame ) + { + mnCurrFrame = nCurrFrame; + mpCurrMtf = maAnimationFrames[ mnCurrFrame ].mpMtf; + mbForceUpdate = true; + } + } + + // hyperlink support + void DrawShape::prepareHyperlinkIndices() const + { + if ( !maHyperlinkIndices.empty()) + { + maHyperlinkIndices.clear(); + maHyperlinkRegions.clear(); + } + + sal_Int32 nIndex = 0; + for ( MetaAction * pCurrAct = mpCurrMtf->FirstAction(); + pCurrAct != nullptr; pCurrAct = mpCurrMtf->NextAction() ) + { + if (pCurrAct->GetType() == MetaActionType::COMMENT) { + MetaCommentAction * pAct = + static_cast<MetaCommentAction *>(pCurrAct); + // skip comment if not a special XTEXT comment + if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN") && + // e.g. date field doesn't have data! + // currently assuming that only url field, this is + // somehow fragile! xxx todo if possible + pAct->GetData() != nullptr && + pAct->GetDataSize() > 0) + { + if (!maHyperlinkIndices.empty() && + maHyperlinkIndices.back().second == -1) { + SAL_WARN( "slideshow", "### pending FIELD_SEQ_END!" ); + maHyperlinkIndices.pop_back(); + maHyperlinkRegions.pop_back(); + } + maHyperlinkIndices.emplace_back( nIndex + 1, + -1 /* to be filled below */ ); + maHyperlinkRegions.emplace_back( + basegfx::B2DRectangle(), + OUString( + reinterpret_cast<sal_Unicode const*>( + pAct->GetData()), + pAct->GetDataSize() / sizeof(sal_Unicode) ) + ); + } + else if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_END") && + // pending end is expected: + !maHyperlinkIndices.empty() && + maHyperlinkIndices.back().second == -1) + { + maHyperlinkIndices.back().second = nIndex; + } + else if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN;PageField")) + { + mbContainsPageField = true; + } + ++nIndex; + } + else + nIndex += getNextActionOffset(pCurrAct); + } + if (!maHyperlinkIndices.empty() && + maHyperlinkIndices.back().second == -1) { + SAL_WARN( "slideshow", "### pending FIELD_SEQ_END!" ); + maHyperlinkIndices.pop_back(); + maHyperlinkRegions.pop_back(); + } + OSL_ASSERT( maHyperlinkIndices.size() == maHyperlinkRegions.size()); + } + + bool DrawShape::hasHyperlinks() const + { + return ! maHyperlinkRegions.empty(); + } + + HyperlinkArea::HyperlinkRegions DrawShape::getHyperlinkRegions() const + { + OSL_ASSERT( !maViewShapes.empty() ); + + if( !isVisible() ) + return HyperlinkArea::HyperlinkRegions(); + + // late init, determine regions: + if( !maHyperlinkRegions.empty() && + !maViewShapes.empty() && + // region already inited? + maHyperlinkRegions.front().first.getWidth() == 0 && + maHyperlinkRegions.front().first.getHeight() == 0 && + maHyperlinkRegions.size() == maHyperlinkIndices.size() ) + { + // TODO(Q2): Although this _is_ currently + // view-agnostic, it might not stay like that. + ViewShapeSharedPtr const& pViewShape = maViewShapes.front(); + cppcanvas::CanvasSharedPtr const pCanvas( + pViewShape->getViewLayer()->getCanvas() ); + + // reuse Renderer of first view shape: + cppcanvas::RendererSharedPtr const pRenderer( + pViewShape->getRenderer( + pCanvas, mpCurrMtf, mpAttributeLayer ) ); + + OSL_ASSERT( pRenderer ); + + if (pRenderer) + { + basegfx::B2DHomMatrix const aOldTransform( + pCanvas->getTransformation() ); + basegfx::B2DHomMatrix aTransform; + pCanvas->setTransformation( aTransform /* empty */ ); + + + ::cppcanvas::Canvas* pTmpCanvas = pCanvas.get(); + comphelper::ScopeGuard const resetOldTransformation( + [&aOldTransform, &pTmpCanvas]() + { return pTmpCanvas->setTransformation( aOldTransform ); } ); + + aTransform.scale( maBounds.getWidth(), + maBounds.getHeight() ); + pRenderer->setTransformation( aTransform ); + pRenderer->setClip(); + + for( std::size_t pos = maHyperlinkRegions.size(); pos--; ) + { + // get region: + HyperlinkIndexPair const& rIndices = maHyperlinkIndices[pos]; + basegfx::B2DRectangle const region( + pRenderer->getSubsetArea( rIndices.first, + rIndices.second )); + maHyperlinkRegions[pos].first = region; + } + } + } + + // shift shape-relative hyperlink regions to + // slide-absolute position + + HyperlinkRegions aTranslatedRegions; + + // increase capacity to same size as the container for + // shape-relative hyperlink regions to avoid reallocation + aTranslatedRegions.reserve( maHyperlinkRegions.size() ); + const basegfx::B2DPoint& rOffset(getBounds().getMinimum()); + for( const auto& cp : maHyperlinkRegions ) + { + basegfx::B2DRange const& relRegion( cp.first ); + aTranslatedRegions.emplace_back( + basegfx::B2DRange( + relRegion.getMinimum() + rOffset, + relRegion.getMaximum() + rOffset), + cp.second ); + } + + return aTranslatedRegions; + } + + double DrawShape::getHyperlinkPriority() const + { + return getPriority(); + } + + + // AnimatableShape methods + + + void DrawShape::enterAnimationMode() + { + OSL_ENSURE( !maViewShapes.empty(), + "DrawShape::enterAnimationMode(): called on DrawShape without views" ); + + if( mnIsAnimatedCount == 0 ) + { + // notify all ViewShapes, by calling their enterAnimationMode method. + // We're now entering animation mode + for( const auto& rViewShape : maViewShapes ) + rViewShape->enterAnimationMode(); + } + + ++mnIsAnimatedCount; + } + + void DrawShape::leaveAnimationMode() + { + OSL_ENSURE( !maViewShapes.empty(), + "DrawShape::leaveAnimationMode(): called on DrawShape without views" ); + + --mnIsAnimatedCount; + + if( mnIsAnimatedCount == 0 ) + { + // notify all ViewShapes, by calling their leaveAnimationMode method. + // we're now leaving animation mode + for( const auto& rViewShape : maViewShapes ) + rViewShape->leaveAnimationMode(); + } + } + + + // AttributableShape methods + + + ShapeAttributeLayerSharedPtr DrawShape::createAttributeLayer() + { + // create new layer, with last as its new child + mpAttributeLayer = std::make_shared<ShapeAttributeLayer>( mpAttributeLayer ); + + // Update the local state ids to reflect those of the new layer. + updateStateIds(); + + return mpAttributeLayer; + } + + bool DrawShape::revokeAttributeLayer( const ShapeAttributeLayerSharedPtr& rLayer ) + { + if( !mpAttributeLayer ) + return false; // no layers + + if( mpAttributeLayer == rLayer ) + { + // it's the toplevel layer + mpAttributeLayer = mpAttributeLayer->getChildLayer(); + + // force content redraw, all state variables have + // possibly changed + mbAttributeLayerRevoked = true; + + return true; + } + else + { + // pass on to the layer, to try its children + return mpAttributeLayer->revokeChildLayer( rLayer ); + } + } + + ShapeAttributeLayerSharedPtr DrawShape::getTopmostAttributeLayer() const + { + return mpAttributeLayer; + } + + void DrawShape::setVisibility( bool bVisible ) + { + if( mbIsVisible != bVisible ) + { + mbIsVisible = bVisible; + mbForceUpdate = true; + } + } + + const DocTreeNodeSupplier& DrawShape::getTreeNodeSupplier() const + { + return *this; + } + + DocTreeNodeSupplier& DrawShape::getTreeNodeSupplier() + { + return *this; + } + + DocTreeNode DrawShape::getSubsetNode() const + { + // forward to delegate + return maSubsetting.getSubsetNode(); + } + + AttributableShapeSharedPtr DrawShape::getSubset( const DocTreeNode& rTreeNode ) const + { + // forward to delegate + return maSubsetting.getSubsetShape( rTreeNode ); + } + + bool DrawShape::createSubset( AttributableShapeSharedPtr& o_rSubset, + const DocTreeNode& rTreeNode ) + { + // subset shape already created for this DocTreeNode? + AttributableShapeSharedPtr pSubset( maSubsetting.getSubsetShape( rTreeNode ) ); + + // when true, this method has created a new subset + // DrawShape + bool bNewlyCreated( false ); + + if( pSubset ) + { + o_rSubset = pSubset; + + // reusing existing subset + } + else + { + // not yet created, init entry + o_rSubset.reset( new DrawShape( *this, + rTreeNode, + // TODO(Q3): That's a + // hack. We assume + // that start and end + // index will always + // be less than 65535 + mnPriority + + rTreeNode.getStartIndex()/double(SAL_MAX_INT16) )); + + bNewlyCreated = true; // subset newly created + } + + // always register shape at DrawShapeSubsetting, to keep + // refcount up-to-date + maSubsetting.addSubsetShape( o_rSubset ); + + // flush bounds cache + maCurrentShapeUnitBounds.reset(); + + return bNewlyCreated; + } + + bool DrawShape::revokeSubset( const AttributableShapeSharedPtr& rShape ) + { + // flush bounds cache + maCurrentShapeUnitBounds.reset(); + + // forward to delegate + if( maSubsetting.revokeSubsetShape( rShape ) ) + { + // force redraw, our content has possibly changed (as + // one of the subsets now display within our shape + // again). + mbForceUpdate = true; + + // #i47428# TEMP FIX: synchronize visibility of subset + // with parent. + + // TODO(F3): Remove here, and implement + // TEXT_ONLY/BACKGROUND_ONLY with the proverbial + // additional level of indirection: create a + // persistent subset, containing all text/only the + // background respectively. From _that_ object, + // generate the temporary character subset shapes. + const ShapeAttributeLayerSharedPtr& rAttrLayer( + rShape->getTopmostAttributeLayer() ); + if( rAttrLayer && + rAttrLayer->isVisibilityValid() && + rAttrLayer->getVisibility() != isVisible() ) + { + const bool bVisibility( rAttrLayer->getVisibility() ); + + // visibilities differ - adjust ours, then + if( mpAttributeLayer ) + mpAttributeLayer->setVisibility( bVisibility ); + else + mbIsVisible = bVisibility; + } + + // END TEMP FIX + + return true; + } + + return false; + } + + sal_Int32 DrawShape::getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException + { + return maSubsetting.getNumberOfTreeNodes( eNodeType ); + } + + DocTreeNode DrawShape::getTreeNode( sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException + { + if ( hasHyperlinks()) + { + prepareHyperlinkIndices(); + } + + return maSubsetting.getTreeNode( nNodeIndex, eNodeType ); + } + + sal_Int32 DrawShape::getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode, + DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException + { + return maSubsetting.getNumberOfSubsetTreeNodes( rParentNode, eNodeType ); + } + + DocTreeNode DrawShape::getSubsetTreeNode( const DocTreeNode& rParentNode, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException + { + return maSubsetting.getSubsetTreeNode( rParentNode, nNodeIndex, eNodeType ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawshape.hxx b/slideshow/source/engine/shapes/drawshape.hxx new file mode 100644 index 000000000..8636a7acd --- /dev/null +++ b/slideshow/source/engine/shapes/drawshape.hxx @@ -0,0 +1,360 @@ +/* -*- 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_SHAPES_DRAWSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPE_HXX + +#include <osl/diagnose.hxx> +#include <com/sun/star/drawing/XShape.hpp> + +#include <attributableshape.hxx> +#include <doctreenodesupplier.hxx> +#include "drawshapesubsetting.hxx" +#include "gdimtftools.hxx" +#include "viewshape.hxx" +#include <hyperlinkarea.hxx> + +#include <optional> +#include <vector> + +class Graphic; + +namespace slideshow::internal + { + class Activity; + struct SlideShowContext; + class DrawShapeSubsetting; + class DrawShape; + typedef ::std::shared_ptr< DrawShape > DrawShapeSharedPtr; + + /** This class is the representation of a draw document's + XShape, and implements the Shape, AnimatableShape, and + AttributableShape interfaces. + + @attention this class is to be treated 'final', i.e. one + should not derive from it. + */ + class DrawShape : public AttributableShape, + public DocTreeNodeSupplier, + public HyperlinkArea, + public ::osl::DebugBase<DrawShape> + { + public: + /** Create a shape for the given XShape + + @param xShape + The XShape to represent. + + @param xContainingPage + The page that contains this shape. Needed for proper + import (currently, the UnoGraphicExporter needs this + information). + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + + @param bForeignSource + When true, the source of the shape metafile might be a + foreign application. The metafile is checked against + unsupported content, and, if necessary, returned as a + pre-rendered bitmap. + */ + static DrawShapeSharedPtr create( + const css::uno::Reference< css::drawing::XShape >& xShape, + const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage, + double nPrio, + bool bForeignSource, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + /** Create a shape for the given XShape and graphic content + + @param xShape + The XShape to represent. + + @param xContainingPage + The page that contains this shape. Needed for proper + import (currently, the UnoGraphicExporter needs this + information). + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + + @param rGraphic + Graphic to display in the shape's bound rect. If this + Graphic contains animatable content, the created + DrawShape will register itself for intrinsic animation + events. + */ + static DrawShapeSharedPtr create( + const css::uno::Reference< css::drawing::XShape >& xShape, + const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage, + double nPrio, + const Graphic& rGraphic, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + virtual css::uno::Reference< css::drawing::XShape > getXShape() const override; + + virtual ~DrawShape() override; + + + // View layer methods + + + virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) override; + virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override; + virtual void clearAllViewLayers() override; + + // attribute methods + + + virtual ShapeAttributeLayerSharedPtr createAttributeLayer() override; + virtual bool revokeAttributeLayer( const ShapeAttributeLayerSharedPtr& rLayer ) override; + virtual ShapeAttributeLayerSharedPtr getTopmostAttributeLayer() const override; + virtual void setVisibility( bool bVisible ) override; + virtual ::basegfx::B2DRectangle getBounds() const override; + virtual ::basegfx::B2DRectangle getDomBounds() const override; + virtual ::basegfx::B2DRectangle getUpdateArea() const override; + virtual bool isVisible() const override; + virtual double getPriority() const override; + + + // animation methods + + + virtual void enterAnimationMode() override; + virtual void leaveAnimationMode() override; + virtual bool isBackgroundDetached() const override; + + // render methods + + + virtual bool update() const override; + virtual bool render() const override; + virtual bool isContentChanged() const override; + + // Sub item specialities + + + virtual const DocTreeNodeSupplier& getTreeNodeSupplier() const override; + virtual DocTreeNodeSupplier& getTreeNodeSupplier() override; + + virtual DocTreeNode getSubsetNode() const override; + virtual AttributableShapeSharedPtr getSubset( const DocTreeNode& rTreeNode ) const override; + virtual bool createSubset( AttributableShapeSharedPtr& o_rSubset, + const DocTreeNode& rTreeNode ) override; + virtual bool revokeSubset( const AttributableShapeSharedPtr& rShape ) override; + + + // DocTreeNodeSupplier methods + + + virtual sal_Int32 getNumberOfTreeNodes ( DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException; + virtual DocTreeNode getTreeNode ( sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException; + virtual sal_Int32 getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode, + DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException; + virtual DocTreeNode getSubsetTreeNode ( const DocTreeNode& rParentNode, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException; + + // HyperlinkArea methods + + + virtual HyperlinkRegions getHyperlinkRegions() const override; + virtual double getHyperlinkPriority() const override; + + + // intrinsic animation methods + + + /** Display next frame of an intrinsic animation. + + Used by IntrinsicAnimationActivity, to show the next + animation frame. + */ + void setIntrinsicAnimationFrame( ::std::size_t nCurrFrame ); + + /** forces the drawshape to load and return a specially + crafted metafile, usable to display drawing layer text + animations. + */ + GDIMetaFileSharedPtr const & forceScrollTextMetaFile(); + + private: + /** Create a shape for the given XShape + + @param xShape + The XShape to represent. + + @param xContainingPage + The page that contains this shape. Needed for proper + import (currently, the UnoGraphicExporter needs this + information). + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + + @param bForeignSource + When true, the source of the shape metafile might be a + foreign application. The metafile is checked against + unsupported content, and, if necessary, returned as a + pre-rendered bitmap. + */ + DrawShape( const css::uno::Reference< + css::drawing::XShape >& xShape, + const css::uno::Reference< + css::drawing::XDrawPage >& xContainingPage, + double nPrio, + bool bForeignSource, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + /** Create a shape for the given XShape and graphic content + + @param xShape + The XShape to represent. + + @param xContainingPage + The page that contains this shape. Needed for proper + import (currently, the UnoGraphicExporter needs this + information). + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + + @param rGraphic + Graphic to display in the shape's bound rect. If this + Graphic contains animatable content, the created + DrawShape will register itself for intrinsic animation + events. + */ + DrawShape( const css::uno::Reference< css::drawing::XShape >& xShape, + const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage, + double nPrio, + const Graphic& rGraphic, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + /** Private copy constructor + + Used to create subsetted shapes + */ + DrawShape( const DrawShape&, const DocTreeNode& rTreeNode, double nPrio ); + + UpdateFlags getUpdateFlags() const; + bool implRender( UpdateFlags nUpdateFlags ) const; + void updateStateIds() const; + + ViewShape::RenderArgs getViewRenderArgs() const; + ::basegfx::B2DRectangle getActualUnitShapeBounds() const; + + bool hasIntrinsicAnimation() const; + bool hasHyperlinks() const; + void prepareHyperlinkIndices() const; + + /// The associated XShape + css::uno::Reference< css::drawing::XShape > mxShape; + css::uno::Reference< css::drawing::XDrawPage > mxPage; + + /** A vector of metafiles actually representing the Shape. + + If this shape is not animated, only a single entry is + available. + */ + mutable VectorOfMtfAnimationFrames maAnimationFrames; + ::std::size_t mnCurrFrame; + + /// Metafile of currently active frame (static for shapes w/o intrinsic animation) + mutable GDIMetaFileSharedPtr mpCurrMtf; + + /// loadflags of current meta file + mutable int mnCurrMtfLoadFlags; + + /// Contains the current shape bounds, in unit rect space + mutable ::std::optional<basegfx::B2DRectangle> maCurrentShapeUnitBounds; + + // The attributes of this Shape + const double mnPriority; + ::basegfx::B2DRectangle maBounds; // always needed for rendering. + // for subset shapes, this member + // might change when views are + // added, as minimal bounds are + // calculated + + // Pointer to modifiable shape attributes + ShapeAttributeLayerSharedPtr mpAttributeLayer; // only created lazily + + // held here, to signal our destruction + std::weak_ptr<Activity> mpIntrinsicAnimationActivity; + + // The attribute states, to detect attribute changes, + // without buffering and querying each single attribute + mutable State::StateId mnAttributeTransformationState; + mutable State::StateId mnAttributeClipState; + mutable State::StateId mnAttributeAlphaState; + mutable State::StateId mnAttributePositionState; + mutable State::StateId mnAttributeContentState; + mutable State::StateId mnAttributeVisibilityState; + + /// the list of active view shapes (one for each registered view layer) + typedef ::std::vector< ViewShapeSharedPtr > ViewShapeVector; + ViewShapeVector maViewShapes; + + css::uno::Reference< css::uno::XComponentContext> mxComponentContext; + + /// hyperlink support + typedef ::std::pair<sal_Int32 /* mtf start */, + sal_Int32 /* mtf end */> HyperlinkIndexPair; + typedef ::std::vector<HyperlinkIndexPair> HyperlinkIndexPairVector; + mutable HyperlinkIndexPairVector maHyperlinkIndices; + mutable HyperlinkRegions maHyperlinkRegions; + + /// Delegated subset handling + mutable DrawShapeSubsetting maSubsetting; + + /// Whether this shape is currently in animation mode (value != 0) + int mnIsAnimatedCount; + + /// Number of times the bitmap animation shall loop + sal_uInt32 mnAnimationLoopCount; + + /// Whether shape is visible (without attribute layers) + bool mbIsVisible; + + /// Whether redraw is necessary, regardless of state ids + mutable bool mbForceUpdate; + + /// Whether attribute layer was revoked (making a redraw necessary) + mutable bool mbAttributeLayerRevoked; + + /// whether a drawing layer animation has to be performed + bool mbDrawingLayerAnim; + + /// tdf#150402 wether mpCurrMtf contains any Text with a PageField ("FIELD_SEQ_BEGIN;PageField") + mutable bool mbContainsPageField; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawshapesubsetting.cxx b/slideshow/source/engine/shapes/drawshapesubsetting.cxx new file mode 100644 index 000000000..fac2e2b57 --- /dev/null +++ b/slideshow/source/engine/shapes/drawshapesubsetting.cxx @@ -0,0 +1,808 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <tools/diagnose_ex.h> + +#include <sal/log.hxx> +#include <utility> +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> + +#include "drawshapesubsetting.hxx" +#include "gdimtftools.hxx" + +#include <algorithm> + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + + + // Private methods + + + void DrawShapeSubsetting::ensureInitializedNodeTree() const + { + ENSURE_OR_THROW( mpMtf, + "DrawShapeSubsetting::ensureInitializedNodeTree(): Invalid mtf" ); + + if( mbNodeTreeInitialized ) + return; // done, already initialized. + + // init doctree vector + maActionClassVector.clear(); + maActionClassVector.reserve( mpMtf->GetActionSize() ); + + // search metafile for text output + MetaAction* pCurrAct; + + sal_Int32 nActionIndex(0); + sal_Int32 nLastTextActionIndex(0); + for( pCurrAct = mpMtf->FirstAction(); pCurrAct; pCurrAct = mpMtf->NextAction() ) + { + // check for one of our special text doctree comments + switch( pCurrAct->GetType() ) + { + case MetaActionType::COMMENT: + { + MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct); + + // skip comment if not a special XTEXT... comment + if( pAct->GetComment().matchIgnoreAsciiCase( "XTEXT" ) ) + { + // fill classification vector with NOOPs, + // then insert corresponding classes at + // the given index + maActionClassVector.resize( nActionIndex+1, CLASS_NOOP ); + + if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOC") ) + { + // special, because can happen + // in-between of portions - set + // character-end classificator at + // given index (relative to last text + // action). + const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() ); + + ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(), + "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" ); + + maActionClassVector[ nIndex ] = CLASS_CHARACTER_CELL_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOW") ) + { + // special, because can happen + // in-between of portions - set + // word-end classificator at given + // index (relative to last text + // action). + const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() ); + + ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(), + "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" ); + + maActionClassVector[ nIndex ] = CLASS_WORD_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOS") ) + { + // special, because can happen + // in-between of portions - set + // sentence-end classificator at given + // index (relative to last text + // action). + const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() ); + + ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(), + "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" ); + + maActionClassVector[ nIndex ] = CLASS_SENTENCE_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOL") ) + { + maActionClassVector[ nActionIndex ] = CLASS_LINE_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOP") ) + { + maActionClassVector[ nActionIndex ] = CLASS_PARAGRAPH_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_END") ) + { + maActionClassVector[ nActionIndex ] = CLASS_SHAPE_END; + } + else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_BEGIN") ) + { + maActionClassVector[ nActionIndex ] = CLASS_SHAPE_START; + } + } + SAL_INFO( + "slideshow", + "Shape text structure: " << pAct->GetComment() + << " at action #" << nActionIndex); + ++nActionIndex; + break; + } + case MetaActionType::TEXT: + case MetaActionType::TEXTARRAY: + case MetaActionType::STRETCHTEXT: + nLastTextActionIndex = nActionIndex; + SAL_INFO("slideshow.verbose", "Shape text \"" << + (static_cast<MetaTextAction*>(pCurrAct))->GetText() << + "\" at action #" << nActionIndex ); + [[fallthrough]]; + default: + // comment action and all actions not + // explicitly handled here: + nActionIndex += getNextActionOffset(pCurrAct); + break; + } + } + + mbNodeTreeInitialized = true; + } + + void DrawShapeSubsetting::excludeSubset(sal_Int32 nExcludedStart, sal_Int32 nExcludedEnd) + { + // If current subsets are empty, fill it with initial range + initCurrentSubsets(); + if (maCurrentSubsets.empty()) + { + // Non-subsetting mode (not a subset of anything; child subsets subtract content) + maCurrentSubsets.emplace_back(0, maActionClassVector.size()); + } + + slideshow::internal::VectorOfDocTreeNodes aNodesToAppend; + for (auto i = maCurrentSubsets.begin(); i != maCurrentSubsets.end();) + { + if (i->getStartIndex() < nExcludedStart) + { + if (i->getEndIndex() > nExcludedStart) + { + // Some overlap -> append new node (if required), and correct this node's end + if (i->getEndIndex() > nExcludedEnd) + { + aNodesToAppend.emplace_back(nExcludedEnd, i->getEndIndex()); + } + i->setEndIndex(nExcludedStart); + } + ++i; + } + else if (i->getStartIndex() < nExcludedEnd) + { + if (i->getEndIndex() > nExcludedEnd) + { + // Partial overlap; change the node's start + i->setStartIndex(nExcludedEnd); + ++i; + } + else + { + // Node is fully inside the removed range: erase it + i = maCurrentSubsets.erase(i); + } + } + else + { + // Node is fully outside (after) excluded range + ++i; + } + } + + maCurrentSubsets.insert(maCurrentSubsets.end(), aNodesToAppend.begin(), + aNodesToAppend.end()); + // Excluding subsets must not leave an absolutely empty maCurrentSubsets, because it + // would mean "non-subsetting" mode unconditionally, with whole object added to subsets. + // So to indicate a subset with all parts excluded, add two empty subsets (starting and + // ending). + if (!maCurrentSubsets.empty()) + return; + + if (maSubset.isEmpty()) + { + maCurrentSubsets.emplace_back(0, 0); + maCurrentSubsets.emplace_back(maActionClassVector.size(), + maActionClassVector.size()); + } + else + { + maCurrentSubsets.emplace_back(maSubset.getStartIndex(), + maSubset.getStartIndex()); + maCurrentSubsets.emplace_back(maSubset.getEndIndex(), maSubset.getEndIndex()); + } + } + + void DrawShapeSubsetting::updateSubsets() + { + maCurrentSubsets.clear(); + initCurrentSubsets(); + + for (const auto& rSubsetShape : maSubsetShapes) + { + excludeSubset(rSubsetShape.mnStartActionIndex, rSubsetShape.mnEndActionIndex); + } + } + + + // Public methods + + + DrawShapeSubsetting::DrawShapeSubsetting() : + maActionClassVector(), + mpMtf(), + maSubset(), + maSubsetShapes(), + maCurrentSubsets(), + mbNodeTreeInitialized( false ) + { + } + + DrawShapeSubsetting::DrawShapeSubsetting( const DocTreeNode& rShapeSubset, + GDIMetaFileSharedPtr rMtf ) : + maActionClassVector(), + mpMtf(std::move( rMtf )), + maSubset( rShapeSubset ), + maSubsetShapes(), + maCurrentSubsets(), + mbNodeTreeInitialized( false ) + { + ENSURE_OR_THROW( mpMtf, + "DrawShapeSubsetting::DrawShapeSubsetting(): Invalid metafile" ); + + initCurrentSubsets(); + } + + void DrawShapeSubsetting::reset() + { + maActionClassVector.clear(); + mpMtf.reset(); + maSubset.reset(); + maSubsetShapes.clear(); + maCurrentSubsets.clear(); + mbNodeTreeInitialized = false; + } + + void DrawShapeSubsetting::reset( const ::std::shared_ptr< GDIMetaFile >& rMtf ) + { + reset(); + mpMtf = rMtf; + + initCurrentSubsets(); + } + + void DrawShapeSubsetting::initCurrentSubsets() + { + // only add subset to vector, if vector is empty, and subset is not empty - that's + // because the vector's content is later literally used + // for e.g. painting. + if (maCurrentSubsets.empty() && !maSubset.isEmpty()) + maCurrentSubsets.push_back( maSubset ); + } + + const DocTreeNode& DrawShapeSubsetting::getSubsetNode() const + { + return maSubset; + } + + AttributableShapeSharedPtr DrawShapeSubsetting::getSubsetShape( const DocTreeNode& rTreeNode ) const + { + SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::getSubsetShape()" ); + + // subset shape already created for this DocTreeNode? + SubsetEntry aEntry; + + aEntry.mnStartActionIndex = rTreeNode.getStartIndex(); + aEntry.mnEndActionIndex = rTreeNode.getEndIndex(); + + ShapeSet::const_iterator aIter; + if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() ) + { + // already created, return found entry + return aIter->mpShape; + } + + return AttributableShapeSharedPtr(); + } + + void DrawShapeSubsetting::addSubsetShape( const AttributableShapeSharedPtr& rShape ) + { + SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::addSubsetShape()" ); + + // subset shape already created for this DocTreeNode? + SubsetEntry aEntry; + const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() ); + + aEntry.mnStartActionIndex = rEffectiveSubset.getStartIndex(); + aEntry.mnEndActionIndex = rEffectiveSubset.getEndIndex(); + + ShapeSet::const_iterator aIter; + if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() ) + { + // already created, increment use count and return + + // safe cast, since set order does not depend on + // mnSubsetQueriedCount + const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount++; + } + else + { + // not yet created, init entry + aEntry.mnSubsetQueriedCount = 1; + aEntry.mpShape = rShape; + + maSubsetShapes.insert( aEntry ); + + excludeSubset(aEntry.mnStartActionIndex, aEntry.mnEndActionIndex); + } + } + + bool DrawShapeSubsetting::revokeSubsetShape( const AttributableShapeSharedPtr& rShape ) + { + SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::revokeSubsetShape()" ); + + // lookup subset shape + SubsetEntry aEntry; + const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() ); + + aEntry.mnStartActionIndex = rEffectiveSubset.getStartIndex(); + aEntry.mnEndActionIndex = rEffectiveSubset.getEndIndex(); + + ShapeSet::iterator aIter; + if( (aIter=maSubsetShapes.find( aEntry )) == maSubsetShapes.end() ) + return false; // not found, subset was never queried + + // last client of the subset revoking? + if( aIter->mnSubsetQueriedCount > 1 ) + { + // no, still clients out there. Just decrement use count + // safe cast, since order does not depend on mnSubsetQueriedCount + const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount--; + + SAL_INFO( + "slideshow", + "Subset summary: shape " << this << ", " + << maSubsetShapes.size() + << " open subsets, revoked subset has refcount " + << aIter->mnSubsetQueriedCount); + + return false; // not the last client + } + + SAL_INFO( + "slideshow", + "Subset summary: shape " << this << ", " + << maSubsetShapes.size() + << " open subsets, cleared subset has range [" + << aEntry.mnStartActionIndex << "," + << aEntry.mnEndActionIndex << "]"); + + // yes, remove from set + maSubsetShapes.erase( aIter ); + + + // update currently active subset for _our_ shape (the + // part of this shape that is visible, i.e. not displayed + // in subset shapes) + + // TODO(P2): This is quite expensive, when + // after every subset effect end, we have to scan + // the whole shape set + + updateSubsets(); + + return true; + } + + namespace + { + /** Iterate over all action classification entries in the + given range, pass each element range found to the + given functor. + + This method extracts, for each of the different action + classifications, the count and the ranges for each of + them, and calls the provided functor with that + information. + + @tpl FunctorT + This is the functor's operator() calling signature, + with eCurrElemClassification denoting the current + classification type the functor is called for, + nCurrElemCount the running total of elements visited + for the given class (starting from 0), and + rCurrElemBegin/rCurrElemEnd the range of the current + element (i.e. the iterators from the start to the end + of this element). + <pre> + bool operator()( IndexClassificator eCurrElemClassification + sal_Int32 nCurrElemCount, + const IndexClassificatorVector::const_iterator& rCurrElemBegin, + const IndexClassificatorVector::const_iterator& rCurrElemEnd ); + </pre> + If the functor returns false, iteration over the + shapes is immediately stopped. + + @param io_pFunctor + This functor is called for every shape found. + + @param rBegin + Start of range to iterate over + + @param rEnd + End of range to iterate over + + @return the number of shapes found in the metafile + */ + template< typename FunctorT > void iterateActionClassifications( + FunctorT& io_rFunctor, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd ) + { + sal_Int32 nCurrShapeCount( 0 ); + sal_Int32 nCurrParaCount( 0 ); + sal_Int32 nCurrLineCount( 0 ); + sal_Int32 nCurrSentenceCount( 0 ); + sal_Int32 nCurrWordCount( 0 ); + sal_Int32 nCurrCharCount( 0 ); + + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastShapeStart(rBegin); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastParaStart(rBegin); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastLineStart(rBegin); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastSentenceStart(rBegin); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastWordStart(rBegin); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastCharStart(rBegin); + + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aNext; + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aCurr( rBegin ); + while( aCurr != rEnd ) + { + // aNext will hold an iterator to the next element + // (or the past-the-end iterator, if aCurr + // references the last element). Used to pass a + // valid half-open range to the functors. + aNext = aCurr; + ++aNext; + + switch( *aCurr ) + { + default: + ENSURE_OR_THROW( false, + "Unexpected type in iterateDocShapes()" ); + case DrawShapeSubsetting::CLASS_NOOP: + // ignore NOOP actions + break; + + case DrawShapeSubsetting::CLASS_SHAPE_START: + // regardless of ending action + // classifications before: a new shape + // always also starts contained elements + // anew + aLastShapeStart = + aLastParaStart = + aLastLineStart = + aLastSentenceStart = + aLastWordStart = + aLastCharStart = aCurr; + break; + + case DrawShapeSubsetting::CLASS_SHAPE_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_SHAPE_END, + nCurrShapeCount, + aLastShapeStart, + aNext ) ) + { + return; + } + + ++nCurrShapeCount; + [[fallthrough]]; // shape end also ends lines + case DrawShapeSubsetting::CLASS_PARAGRAPH_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_PARAGRAPH_END, + nCurrParaCount, + aLastParaStart, + aNext ) ) + { + return; + } + + ++nCurrParaCount; + aLastParaStart = aNext; + [[fallthrough]]; // para end also ends line + case DrawShapeSubsetting::CLASS_LINE_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_LINE_END, + nCurrLineCount, + aLastLineStart, + aNext ) ) + { + return; + } + + ++nCurrLineCount; + aLastLineStart = aNext; + + if( *aCurr == DrawShapeSubsetting::CLASS_LINE_END ) + { + // DON'T fall through here, as a line + // does NOT end neither a sentence, + // nor a word. OTOH, all parent + // structures (paragraph and shape), + // which itself fall through to this + // code, DO end word, sentence and + // character cell. + + // TODO(F1): Maybe a line should end a + // character cell, OTOH? + break; + } + [[fallthrough]]; + case DrawShapeSubsetting::CLASS_SENTENCE_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_SENTENCE_END, + nCurrSentenceCount, + aLastSentenceStart, + aNext ) ) + { + return; + } + + ++nCurrSentenceCount; + aLastSentenceStart = aNext; + [[fallthrough]]; + case DrawShapeSubsetting::CLASS_WORD_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_WORD_END, + nCurrWordCount, + aLastWordStart, + aNext ) ) + { + return; + } + + ++nCurrWordCount; + aLastWordStart = aNext; + [[fallthrough]]; + case DrawShapeSubsetting::CLASS_CHARACTER_CELL_END: + if( !io_rFunctor( DrawShapeSubsetting::CLASS_CHARACTER_CELL_END, + nCurrCharCount, + aLastCharStart, + aNext ) ) + { + return; + } + + ++nCurrCharCount; + aLastCharStart = aNext; + break; + } + + aCurr = aNext; + } + } + + DrawShapeSubsetting::IndexClassificator mapDocTreeNode( DocTreeNode::NodeType eNodeType ) + { + switch( eNodeType ) + { + default: + SAL_WARN( "slideshow", "DrawShapeSubsetting::mapDocTreeNode(): unexpected node type"); + return DrawShapeSubsetting::CLASS_NOOP; + + case DocTreeNode::NodeType::LogicalParagraph: + return DrawShapeSubsetting::CLASS_PARAGRAPH_END; + + case DocTreeNode::NodeType::LogicalWord: + return DrawShapeSubsetting::CLASS_WORD_END; + + case DocTreeNode::NodeType::LogicalCharacterCell: + return DrawShapeSubsetting::CLASS_CHARACTER_CELL_END; + }; + } + + /// Counts number of class occurrences + class CountClassFunctor + { + public: + explicit CountClassFunctor( DrawShapeSubsetting::IndexClassificator eClass ) : + meClass( eClass ), + mnCurrCount(0) + { + } + + bool operator()( DrawShapeSubsetting::IndexClassificator eCurrElemClassification, + sal_Int32 /*nCurrElemCount*/, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& /*rCurrElemBegin*/, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& /*rCurrElemEnd*/ ) + { + if( eCurrElemClassification == meClass ) + ++mnCurrCount; + + return true; // never stop, count all occurrences + } + + sal_Int32 getCount() const + { + return mnCurrCount; + } + + private: + DrawShapeSubsetting::IndexClassificator meClass; + sal_Int32 mnCurrCount; + }; + } + + sal_Int32 DrawShapeSubsetting::implGetNumberOfTreeNodes( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd, + DocTreeNode::NodeType eNodeType ) + { + const IndexClassificator eRequestedClass( + mapDocTreeNode( eNodeType ) ); + + // create a counting functor for the requested class of + // actions + CountClassFunctor aFunctor( eRequestedClass ); + + // count all occurrences in the given range + iterateActionClassifications( aFunctor, rBegin, rEnd ); + + return aFunctor.getCount(); + } + + sal_Int32 DrawShapeSubsetting::getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const + { + ensureInitializedNodeTree(); + + return implGetNumberOfTreeNodes( maActionClassVector.begin(), + maActionClassVector.end(), + eNodeType ); + } + + namespace + { + /** This functor finds the nth occurrence of a given + action class. + + The operator() compares the given index value with the + requested index, as given on the functor's + constructor. Then, the operator() returns false, + denoting that the requested action is found. + */ + class FindNthElementFunctor + { + public: + FindNthElementFunctor( sal_Int32 nNodeIndex, + DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rLastBegin, + DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rLastEnd, + DrawShapeSubsetting::IndexClassificator eClass ) : + mnNodeIndex( nNodeIndex ), + mrLastBegin( rLastBegin ), + mrLastEnd( rLastEnd ), + meClass( eClass ) + { + } + + bool operator()( DrawShapeSubsetting::IndexClassificator eCurrElemClassification, + sal_Int32 nCurrElemCount, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rCurrElemBegin, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rCurrElemEnd ) + { + if( eCurrElemClassification == meClass && + nCurrElemCount == mnNodeIndex ) + { + mrLastBegin = rCurrElemBegin; + mrLastEnd = rCurrElemEnd; + + return false; // abort iteration, we've + // already found what we've been + // looking for + } + + return true; // keep on truckin' + } + + private: + sal_Int32 mnNodeIndex; + DrawShapeSubsetting::IndexClassificatorVector::const_iterator& mrLastBegin; + DrawShapeSubsetting::IndexClassificatorVector::const_iterator& mrLastEnd; + DrawShapeSubsetting::IndexClassificator meClass; + }; + + DocTreeNode makeTreeNode( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rStart, + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd ) + { + return DocTreeNode( ::std::distance(rBegin, + rStart), + ::std::distance(rBegin, + rEnd) ); + } + } + + DocTreeNode DrawShapeSubsetting::implGetTreeNode( const IndexClassificatorVector::const_iterator& rBegin, + const IndexClassificatorVector::const_iterator& rEnd, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const + { + const IndexClassificator eRequestedClass( + mapDocTreeNode( eNodeType ) ); + + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastBegin(rEnd); + DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastEnd(rEnd); + + // create a nth element functor for the requested class of + // actions, and nNodeIndex as the target index + FindNthElementFunctor aFunctor( nNodeIndex, + aLastBegin, + aLastEnd, + eRequestedClass ); + + // find given index in the given range + iterateActionClassifications( aFunctor, rBegin, rEnd ); + + return makeTreeNode( maActionClassVector.begin(), + aLastBegin, aLastEnd ); + } + + DocTreeNode DrawShapeSubsetting::getTreeNode( sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const + { + ensureInitializedNodeTree(); + + return implGetTreeNode( maActionClassVector.begin(), + maActionClassVector.end(), + nNodeIndex, + eNodeType ); + } + + sal_Int32 DrawShapeSubsetting::getNumberOfSubsetTreeNodes( const DocTreeNode& rParentNode, + DocTreeNode::NodeType eNodeType ) const + { + ensureInitializedNodeTree(); + + // convert from vector indices to vector iterators + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() ); + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() ); + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() ); + + return implGetNumberOfTreeNodes( aParentBegin, + aParentEnd, + eNodeType ); + } + + DocTreeNode DrawShapeSubsetting::getSubsetTreeNode( const DocTreeNode& rParentNode, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const + { + ensureInitializedNodeTree(); + + // convert from vector indices to vector iterators + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() ); + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() ); + const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() ); + + return implGetTreeNode( aParentBegin, + aParentEnd, + nNodeIndex, + eNodeType ); + } + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawshapesubsetting.hxx b/slideshow/source/engine/shapes/drawshapesubsetting.hxx new file mode 100644 index 000000000..fe0347774 --- /dev/null +++ b/slideshow/source/engine/shapes/drawshapesubsetting.hxx @@ -0,0 +1,244 @@ +/* -*- 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_SHAPES_DRAWSHAPESUBSETTING_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPESUBSETTING_HXX + +#include <doctreenode.hxx> +#include <attributableshape.hxx> + + +class GDIMetaFile; +typedef ::std::shared_ptr< GDIMetaFile > GDIMetaFileSharedPtr; + +namespace slideshow::internal + { + /** This class encapsulates the subsetting aspects of a + DrawShape. + */ + class DrawShapeSubsetting + { + public: + /** Create empty shape subset handling. + + This method creates a subset handler which contains no + subset information. All methods will return default + values. + + @param rMtf + Metafile to retrieve subset info from (must have been + generated with verbose text comments switched on). + */ + DrawShapeSubsetting(); + + /** Create new shape subset handling. + + @param rShapeSubset + The subset this object represents (can be empty, then + denoting 'represents a whole shape') + + @param rMtf + Metafile to retrieve subset info from (must have been + generated with verbose text comments switched on). + */ + DrawShapeSubsetting( const DocTreeNode& rShapeSubset, + GDIMetaFileSharedPtr rMtf ); + + /// Forbid copy construction + DrawShapeSubsetting(const DrawShapeSubsetting&) = delete; + + /// Forbid copy assignment + DrawShapeSubsetting& operator=(const DrawShapeSubsetting&) = delete; + + /** Reset metafile. + + Use this method to completely reset the + ShapeSubsetting, with a new metafile. Note that any + information previously set will be lost, including + added subset shapes! + + @param rMtf + Metafile to retrieve subset info from (must have been + generated with verbose text comments switched on). + */ + void reset( const ::std::shared_ptr< GDIMetaFile >& rMtf ); + + // Shape subsetting methods + + + /// Return subset node for this shape + const DocTreeNode& getSubsetNode () const; + + /// Get subset shape for given node, if any + AttributableShapeSharedPtr getSubsetShape ( const DocTreeNode& rTreeNode ) const; + + /// Add child subset shape (or increase use count, if already existent) + void addSubsetShape ( const AttributableShapeSharedPtr& rShape ); + + /** Revoke subset shape + + This method revokes a subset shape, decrementing the + use count for this subset by one. If the use count + reaches zero (i.e. when the number of addSubsetShape() + matches the number of revokeSubsetShape() calls for + the same subset), the subset entry is removed from the + internal list, and subsequent getSubsetShape() calls + will return the empty pointer for this subset. + + @return true, if the subset shape was physically + removed from the list (false is returned, when nothing + was removed, either because only the use count was + decremented, or there was no such subset found, in the + first place). + */ + bool revokeSubsetShape ( const AttributableShapeSharedPtr& rShape ); + + + // Doc tree methods + + + /// Return overall number of nodes for given type + sal_Int32 getNumberOfTreeNodes ( DocTreeNode::NodeType eNodeType ) const; + + /// Return tree node of given index and given type + DocTreeNode getTreeNode ( sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const; + + /// Return number of nodes of given type, below parent node + sal_Int32 getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode, + DocTreeNode::NodeType eNodeType ) const; + + /// Return tree node of given index and given type, relative to parent node + DocTreeNode getSubsetTreeNode ( const DocTreeNode& rParentNode, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const; + + // Helper + + + /** Return a vector of currently active subsets. + + Needed when rendering a shape, this method provides a + vector of subsets currently visible (the range as + returned by getEffectiveSubset(), minus the parts that + are currently hidden, because displayed by child + shapes). + */ + const VectorOfDocTreeNodes& getActiveSubsets() const { return maCurrentSubsets; } + + /** This enum classifies each action index in the + metafile. + + Of interest are, of course, the places where + structural shape and/or text elements end. The + remainder of the action gets classified as 'noop' + */ + enum IndexClassificator + { + CLASS_NOOP, + CLASS_SHAPE_START, + CLASS_SHAPE_END, + + CLASS_LINE_END, + CLASS_PARAGRAPH_END, + CLASS_SENTENCE_END, + CLASS_WORD_END, + CLASS_CHARACTER_CELL_END + }; + + typedef ::std::vector< IndexClassificator > IndexClassificatorVector; + + private: + /** Entry for subset shape + + This struct contains data for every subset shape + generated. Note that for a given start/end action + index combination, only one subset instance is + generated (and reused for subsequent queries). + */ + struct SubsetEntry + { + AttributableShapeSharedPtr mpShape; + sal_Int32 mnStartActionIndex; + sal_Int32 mnEndActionIndex; + + /// Number of times this subset was queried, and not yet revoked + int mnSubsetQueriedCount; + + sal_Int32 getHashValue() const + { + // TODO(Q3): That's a hack. We assume that start + // index will always be less than 65535 (if this + // assumption is violated, hash map performance + // will degrade severely) + return mnStartActionIndex*SAL_MAX_INT16 + mnEndActionIndex; + } + + /// The shape set is ordered according to this method + bool operator<(const SubsetEntry& rOther) const + { + return getHashValue() < rOther.getHashValue(); + } + + }; + + typedef ::std::set< SubsetEntry > ShapeSet; + + void ensureInitializedNodeTree() const; + void excludeSubset(sal_Int32 nExcludedStart, sal_Int32 nExcludedEnd); + void updateSubsets(); + void initCurrentSubsets(); + void reset(); + + static sal_Int32 implGetNumberOfTreeNodes( const IndexClassificatorVector::const_iterator& rBegin, + const IndexClassificatorVector::const_iterator& rEnd, + DocTreeNode::NodeType eNodeType ); + DocTreeNode implGetTreeNode( const IndexClassificatorVector::const_iterator& rBegin, + const IndexClassificatorVector::const_iterator& rEnd, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const; + + mutable IndexClassificatorVector maActionClassVector; + + /// Metafile to retrieve subset info from + ::std::shared_ptr< GDIMetaFile > mpMtf; + + /// Subset of the metafile represented by this object + DocTreeNode maSubset; + + /// the list of subset shapes spawned from this one. + ShapeSet maSubsetShapes; + + /** Current number of subsets to render (calculated from + maSubset and mnMin/MaxSubsetActionIndex). + + Note that this is generally _not_ equivalent to + maSubset, as it excludes all active subset children! + */ + mutable VectorOfDocTreeNodes maCurrentSubsets; + + /// Whether the shape's doc tree has been initialized successfully, or not + mutable bool mbNodeTreeInitialized; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPESUBSETTING_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/externalshapebase.cxx b/slideshow/source/engine/shapes/externalshapebase.cxx new file mode 100644 index 000000000..440b10d95 --- /dev/null +++ b/slideshow/source/engine/shapes/externalshapebase.cxx @@ -0,0 +1,211 @@ +/* -*- 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 <tools/diagnose_ex.h> + +#include "externalshapebase.hxx" +#include <eventmultiplexer.hxx> +#include <subsettableshapemanager.hxx> +#include <vieweventhandler.hxx> +#include <intrinsicanimationeventhandler.hxx> +#include <tools.hxx> + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + class ExternalShapeBase::ExternalShapeBaseListener : public ViewEventHandler, + public IntrinsicAnimationEventHandler + { + public: + explicit ExternalShapeBaseListener( ExternalShapeBase& rBase ) : + mrBase( rBase ) + {} + ExternalShapeBaseListener(const ExternalShapeBaseListener&) = delete; + ExternalShapeBaseListener& operator=(const ExternalShapeBaseListener&) = delete; + + private: + // ViewEventHandler + + + virtual void viewAdded( const UnoViewSharedPtr& ) override {} + virtual void viewRemoved( const UnoViewSharedPtr& ) override {} + virtual void viewChanged( const UnoViewSharedPtr& rView ) override + { + mrBase.implViewChanged(rView); + } + virtual void viewsChanged() override + { + mrBase.implViewsChanged(); + } + + + // IntrinsicAnimationEventHandler + + + virtual bool enableAnimations() override + { + return mrBase.implStartIntrinsicAnimation(); + } + virtual bool disableAnimations() override + { + return mrBase.implEndIntrinsicAnimation(); + } + + ExternalShapeBase& mrBase; + }; + + + ExternalShapeBase::ExternalShapeBase( const uno::Reference< drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext ) : + mxComponentContext( rContext.mxComponentContext ), + mxShape( xShape ), + mpListener( std::make_shared<ExternalShapeBaseListener>(*this) ), + mpShapeManager( rContext.mpSubsettableShapeManager ), + mrEventMultiplexer( rContext.mrEventMultiplexer ), + mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ), + maBounds( getAPIShapeBounds( xShape ) ) + { + ENSURE_OR_THROW( mxShape.is(), "ExternalShapeBase::ExternalShapeBase(): Invalid XShape" ); + + mpShapeManager->addIntrinsicAnimationHandler( mpListener ); + mrEventMultiplexer.addViewHandler( mpListener ); + } + + + ExternalShapeBase::~ExternalShapeBase() + { + try + { + mrEventMultiplexer.removeViewHandler( mpListener ); + mpShapeManager->removeIntrinsicAnimationHandler( mpListener ); + } + catch (uno::Exception &) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + + + uno::Reference< drawing::XShape > ExternalShapeBase::getXShape() const + { + return mxShape; + } + + + void ExternalShapeBase::play() + { + implStartIntrinsicAnimation(); + } + + + void ExternalShapeBase::stop() + { + implEndIntrinsicAnimation(); + } + + + void ExternalShapeBase::pause() + { + implPauseIntrinsicAnimation(); + } + + + bool ExternalShapeBase::isPlaying() const + { + return implIsIntrinsicAnimationPlaying(); + } + + + void ExternalShapeBase::setMediaTime(double fTime) + { + implSetIntrinsicAnimationTime(fTime); + } + + void ExternalShapeBase::setLooping(bool bLooping) { implSetLooping(bLooping); } + + bool ExternalShapeBase::update() const + { + return render(); + } + + + bool ExternalShapeBase::render() const + { + if( maBounds.getRange().equalZero() ) + { + // zero-sized shapes are effectively invisible, + // thus, we save us the rendering... + return true; + } + + return implRender( maBounds ); + } + + + bool ExternalShapeBase::isContentChanged() const + { + return true; + } + + + ::basegfx::B2DRectangle ExternalShapeBase::getBounds() const + { + return maBounds; + } + + + ::basegfx::B2DRectangle ExternalShapeBase::getDomBounds() const + { + return maBounds; + } + + + ::basegfx::B2DRectangle ExternalShapeBase::getUpdateArea() const + { + return maBounds; + } + + + bool ExternalShapeBase::isVisible() const + { + return true; + } + + + double ExternalShapeBase::getPriority() const + { + return mnPriority; + } + + + bool ExternalShapeBase::isBackgroundDetached() const + { + // external shapes always have their own window/surface + return true; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/externalshapebase.hxx b/slideshow/source/engine/shapes/externalshapebase.hxx new file mode 100644 index 000000000..d8c208db2 --- /dev/null +++ b/slideshow/source/engine/shapes/externalshapebase.hxx @@ -0,0 +1,131 @@ +/* -*- 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_SHAPES_EXTERNALSHAPEBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_EXTERNALSHAPEBASE_HXX + +#include <iexternalmediashapebase.hxx> +#include <unoview.hxx> +#include <slideshowcontext.hxx> + + +namespace slideshow::internal + { + /** Base class for shapes rendered by external engines. + + Used as the common base for e.g. MediaShape or + AppletShape, all of which are rendered by external + components (and all employ distinct windows). + + Please note that this base class indeed assumes the shape + does not interfere with the internal shapes in any way + (including mutual overdraw). It therefore reports yes for + the isBackgroundDetached() question. + */ + class ExternalShapeBase : public IExternalMediaShapeBase + { + public: + /** Create a shape for the given XShape for an external shape + + @param xShape + The XShape to represent. + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + */ + ExternalShapeBase( const css::uno::Reference< css::drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + virtual ~ExternalShapeBase() override; + + virtual css::uno::Reference< css::drawing::XShape > getXShape() const override; + + // animation methods + + + virtual void play() override; + virtual void stop() override; + virtual void pause() override; + virtual bool isPlaying() const override; + virtual void setMediaTime(double) override; + void setLooping(bool bLooping) override; + + // render methods + + + virtual bool update() const override; + virtual bool render() const override; + virtual bool isContentChanged() const override; + + + // Shape attributes + + + virtual ::basegfx::B2DRectangle getBounds() const override; + virtual ::basegfx::B2DRectangle getDomBounds() const override; + virtual ::basegfx::B2DRectangle getUpdateArea() const override; + virtual bool isVisible() const override; + virtual double getPriority() const override; + virtual bool isBackgroundDetached() const override; + + protected: + const css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + + private: + class ExternalShapeBaseListener; friend class ExternalShapeBaseListener; + + /// override in derived class to render preview + virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const = 0; + + /// override in derived class to resize + virtual void implViewChanged( const UnoViewSharedPtr& rView ) = 0; + /// override in derived class to resize + virtual void implViewsChanged() = 0; + + /// override in derived class to start external viewer + virtual bool implStartIntrinsicAnimation() = 0; + /// override in derived class to stop external viewer + virtual bool implEndIntrinsicAnimation() = 0; + /// override in derived class to pause external viewer + virtual void implPauseIntrinsicAnimation() = 0; + /// override in derived class to return status of animation + virtual bool implIsIntrinsicAnimationPlaying() const = 0; + /// override in derived class to set media time + virtual void implSetIntrinsicAnimationTime(double) = 0; + virtual void implSetLooping(bool /*bLooping*/) {} + + + /// The associated XShape + css::uno::Reference< css::drawing::XShape > mxShape; + + std::shared_ptr<ExternalShapeBaseListener> mpListener; + + SubsettableShapeManagerSharedPtr mpShapeManager; + EventMultiplexer& mrEventMultiplexer; + + // The attributes of this Shape + const double mnPriority; + ::basegfx::B2DRectangle maBounds; + }; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_EXTERNALSHAPEBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/gdimtftools.cxx b/slideshow/source/engine/shapes/gdimtftools.cxx new file mode 100644 index 000000000..6a803f757 --- /dev/null +++ b/slideshow/source/engine/shapes/gdimtftools.cxx @@ -0,0 +1,421 @@ +/* -*- 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 "gdimtftools.hxx" + +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/XGraphicRenderer.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> + +#include <comphelper/fileformat.h> +#include <comphelper/propertyvalue.hxx> + +#include <vcl/canvastools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/animate/Animation.hxx> +#include <vcl/graph.hxx> + +#include <tools.hxx> + +using namespace ::com::sun::star; + + +// free support functions +// ====================== + +namespace slideshow::internal +{ +// TODO(E2): Detect the case when svx/drawing layer is not +// in-process, or even not on the same machine, and +// fallback to metafile streaming! + +// For fixing #i48102#, have to be a _lot_ more selective +// on which metafiles to convert to bitmaps. The problem +// here is that we _always_ get the shape content as a +// metafile, even if we have a bitmap graphic shape. Thus, +// calling GetBitmapEx on such a Graphic (see below) will +// result in one poorly scaled bitmap into another, +// somewhat arbitrarily sized bitmap. +static bool hasUnsupportedActions( const GDIMetaFile& rMtf ) +{ + // search metafile for RasterOp action + MetaAction* pCurrAct; + + // TODO(Q3): avoid const-cast + for( pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction(); + pCurrAct; + pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction() ) + { + switch( pCurrAct->GetType() ) + { + case MetaActionType::RASTEROP: + // overpaint is okay - that's the default, anyway + if( RasterOp::OverPaint == + static_cast<MetaRasterOpAction*>(pCurrAct)->GetRasterOp() ) + { + break; + } + [[fallthrough]]; + case MetaActionType::MOVECLIPREGION: + case MetaActionType::REFPOINT: + case MetaActionType::WALLPAPER: + return true; // at least one unsupported + // action encountered + default: break; + } + } + + return false; // no unsupported action found +} + +namespace { + +typedef ::cppu::WeakComponentImplHelper< graphic::XGraphicRenderer > DummyRenderer_Base; + +class DummyRenderer: public cppu::BaseMutex, public DummyRenderer_Base +{ +public: + DummyRenderer() : + DummyRenderer_Base( m_aMutex ), + mxGraphic() + { + } + + //--- XGraphicRenderer ----------------------------------- + virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + mxGraphic = rGraphic; + } + + /** Retrieve GDIMetaFile from renderer + + @param bForeignSource + When true, the source of the metafile might be a + foreign application. The metafile is checked + against unsupported content, and, if necessary, + returned as a pre-rendered bitmap. + */ + GDIMetaFileSharedPtr getMtf( bool bForeignSource ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + Graphic aGraphic( mxGraphic ); + + if( aGraphic.GetType() == GraphicType::Bitmap || + (bForeignSource && + hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) ) + { + // wrap bitmap into GDIMetafile + GDIMetaFileSharedPtr xMtf = std::make_shared<GDIMetaFile>(); + + ::BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); + + xMtf->AddAction( new MetaBmpExAction( Point(), + aBmpEx ) ); + xMtf->SetPrefSize( aBmpEx.GetPrefSize() ); + xMtf->SetPrefMapMode( aBmpEx.GetPrefMapMode() ); + + return xMtf; + } + return std::make_shared<GDIMetaFile>(aGraphic.GetGDIMetaFile()); + } + +private: + uno::Reference< graphic::XGraphic > mxGraphic; +}; + +} // anon namespace + +// Quick'n'dirty way: tunnel Graphic (only works for +// in-process slideshow, of course) +GDIMetaFileSharedPtr getMetaFile( const uno::Reference< lang::XComponent >& xSource, + const uno::Reference< drawing::XDrawPage >& xContainingPage, + int mtfLoadFlags, + const uno::Reference< uno::XComponentContext >& rxContext ) +{ + if (!rxContext.is()) + { + SAL_WARN("slideshow.opengl", "getMetaFile(): Invalid context" ); + return GDIMetaFileSharedPtr(); + } + + // create dummy XGraphicRenderer, which receives the + // generated XGraphic from the GraphicExporter + + // TODO(P3): Move creation of DummyRenderer out of the + // loop! Either by making it static, or transforming + // the whole thing here into a class. + rtl::Reference<DummyRenderer> xRenderer( new DummyRenderer() ); + + // creating the graphic exporter + uno::Reference< drawing::XGraphicExportFilter > xExporter = + drawing::GraphicExportFilter::create(rxContext); + + uno::Sequence< beans::PropertyValue > aFilterData{ + comphelper::makePropertyValue("ScrollText", + ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0)), + comphelper::makePropertyValue("ExportOnlyBackground", + ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0)), + comphelper::makePropertyValue("Version", static_cast<sal_Int32>( SOFFICE_FILEFORMAT_50 )), + comphelper::makePropertyValue( + "CurrentPage", uno::Reference< uno::XInterface >( xContainingPage, + uno::UNO_QUERY_THROW )) + }; + + uno::Sequence< beans::PropertyValue > aProps{ + comphelper::makePropertyValue("FilterName", OUString("SVM")), + comphelper::makePropertyValue("GraphicRenderer", uno::Reference< graphic::XGraphicRenderer >(xRenderer)), + comphelper::makePropertyValue("FilterData", aFilterData) + }; + + xExporter->setSourceDocument( xSource ); + if( !xExporter->filter( aProps ) ) + return GDIMetaFileSharedPtr(); + + GDIMetaFileSharedPtr xMtf = xRenderer->getMtf( (mtfLoadFlags & MTF_LOAD_FOREIGN_SOURCE) != 0 ); + + // pRenderer is automatically destroyed when xRenderer + // goes out of scope + + // TODO(E3): Error handling. Exporter might have + // generated nothing, a bitmap, threw an exception, + // whatever. + return xMtf; +} + +sal_Int32 getNextActionOffset( MetaAction * pCurrAct ) +{ + // Special handling for actions that represent + // more than one indexable action + // =========================================== + + switch (pCurrAct->GetType()) { + case MetaActionType::TEXT: { + MetaTextAction * pAct = static_cast<MetaTextAction *>(pCurrAct); + sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + return nLen; + } + case MetaActionType::TEXTARRAY: { + MetaTextArrayAction * pAct = + static_cast<MetaTextArrayAction *>(pCurrAct); + sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + return nLen; + } + case MetaActionType::STRETCHTEXT: { + MetaStretchTextAction * pAct = + static_cast<MetaStretchTextAction *>(pCurrAct); + sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + return nLen; + } + case MetaActionType::FLOATTRANSPARENT: { + MetaFloatTransparentAction * pAct = + static_cast<MetaFloatTransparentAction*>(pCurrAct); + // TODO(F2): Recurse into action metafile + // (though this is currently not used from the + // DrawingLayer - shape transparency gradients + // don't affect shape text) + return pAct->GetGDIMetaFile().GetActionSize(); + } + default: + return 1; + } +} + +bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, + sal_uInt32& o_rLoopCount, + const Graphic& rGraphic ) +{ + o_rFrames.clear(); + + if( !rGraphic.IsAnimated() ) + return false; + + // some loop invariants + ::Animation aAnimation( rGraphic.GetAnimation() ); + const Point aEmptyPoint; + const Size aAnimSize( aAnimation.GetDisplaySizePixel() ); + + // setup VDev, into which all bitmaps are painted (want to + // normalize animations to n bitmaps of same size. An Animation, + // though, can contain bitmaps of varying sizes and different + // update modes) + ScopedVclPtrInstance< VirtualDevice > pVDev; + pVDev->SetOutputSizePixel( aAnimSize ); + pVDev->EnableMapMode( false ); + + // setup mask VDev (alpha VDev is currently rather slow) + ScopedVclPtrInstance<VirtualDevice> pVDevMask(DeviceFormat::DEFAULT); + pVDevMask->SetOutputSizePixel( aAnimSize ); + pVDevMask->EnableMapMode( false ); + + o_rLoopCount = aAnimation.GetLoopCount(); + + for( sal_uInt16 i=0, nCount=aAnimation.Count(); i<nCount; ++i ) + { + const AnimationBitmap& rAnimationBitmap( aAnimation.Get(i) ); + switch(rAnimationBitmap.meDisposal) + { + case Disposal::Not: + { + pVDev->DrawBitmapEx(rAnimationBitmap.maPositionPixel, + rAnimationBitmap.maBitmapEx); + Bitmap aMask = rAnimationBitmap.maBitmapEx.GetAlpha(); + + if( aMask.IsEmpty() ) + { + const tools::Rectangle aRect(aEmptyPoint, + pVDevMask->GetOutputSizePixel()); + const Wallpaper aWallpaper(COL_BLACK); + pVDevMask->DrawWallpaper(aRect, + aWallpaper); + } + else + { + BitmapEx aTmpMask(aMask, aMask); + pVDevMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, + aTmpMask ); + } + break; + } + + case Disposal::Back: + { + // #i70772# react on no mask + const Bitmap aMask(rAnimationBitmap.maBitmapEx.GetAlpha()); + const Bitmap & rContent(rAnimationBitmap.maBitmapEx.GetBitmap()); + + pVDevMask->Erase(); + pVDev->DrawBitmap(rAnimationBitmap.maPositionPixel, rContent); + + if(aMask.IsEmpty()) + { + const tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, rContent.GetSizePixel()); + pVDevMask->SetFillColor( COL_BLACK); + pVDevMask->SetLineColor(); + pVDevMask->DrawRect(aRect); + } + else + { + pVDevMask->DrawBitmap(rAnimationBitmap.maPositionPixel, aMask); + } + break; + } + + case Disposal::Previous : + { + pVDev->DrawBitmapEx(rAnimationBitmap.maPositionPixel, + rAnimationBitmap.maBitmapEx); + pVDevMask->DrawBitmap(rAnimationBitmap.maPositionPixel, + rAnimationBitmap.maBitmapEx.GetAlpha()); + break; + } + } + + // extract current aVDev content into a new animation + // frame + GDIMetaFileSharedPtr pMtf = std::make_shared<GDIMetaFile>(); + pMtf->AddAction( + new MetaBmpExAction( aEmptyPoint, + BitmapEx( + pVDev->GetBitmap( + aEmptyPoint, + aAnimSize ), + pVDevMask->GetBitmap( + aEmptyPoint, + aAnimSize )))); + + // setup mtf dimensions and pref map mode (for + // simplicity, keep it all in pixel. the metafile + // renderer scales it down to (1, 1) box anyway) + pMtf->SetPrefMapMode( MapMode() ); + pMtf->SetPrefSize( aAnimSize ); + + // Take care of special value for MultiPage TIFFs. ATM these shall just + // show their first page for _quite_ some time. + sal_Int32 nWaitTime100thSeconds(rAnimationBitmap.mnWait); + if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds ) + { + // ATM the huge value would block the timer, so use a long + // time to show first page (whole day) + nWaitTime100thSeconds = 100 * 60 * 60 * 24; + } + + // There are animated GIFs with no WaitTime set. Take 0.1 sec, the + // same duration that is used by the edit view. + if( nWaitTime100thSeconds == 0 ) + nWaitTime100thSeconds = 10; + + o_rFrames.emplace_back( pMtf, nWaitTime100thSeconds / 100.0 ); + } + + return !o_rFrames.empty(); +} + +bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect, + ::basegfx::B2DRectangle& o_rPaintRect, + const GDIMetaFileSharedPtr& rMtf ) +{ + // extract bounds: scroll rect, paint rect + bool bScrollRectSet(false); + bool bPaintRectSet(false); + + for ( MetaAction * pCurrAct = rMtf->FirstAction(); + pCurrAct != nullptr; pCurrAct = rMtf->NextAction() ) + { + if (pCurrAct->GetType() == MetaActionType::COMMENT) + { + MetaCommentAction * pAct = + static_cast<MetaCommentAction *>(pCurrAct); + // skip comment if not a special XTEXT... comment + if( pAct->GetComment().matchIgnoreAsciiCase( "XTEXT" ) ) + { + if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_SCROLLRECT")) + { + o_rScrollRect = vcl::unotools::b2DRectangleFromRectangle( + *reinterpret_cast<tools::Rectangle const *>( + pAct->GetData() )); + + bScrollRectSet = true; + } + else if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTRECT") ) + { + o_rPaintRect = vcl::unotools::b2DRectangleFromRectangle( + *reinterpret_cast<tools::Rectangle const *>( + pAct->GetData() )); + + bPaintRectSet = true; + } + } + } + } + + return bScrollRectSet && bPaintRectSet; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/gdimtftools.hxx b/slideshow/source/engine/shapes/gdimtftools.hxx new file mode 100644 index 000000000..7812301f9 --- /dev/null +++ b/slideshow/source/engine/shapes/gdimtftools.hxx @@ -0,0 +1,129 @@ +/* -*- 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_SHAPES_GDIMTFTOOLS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_GDIMTFTOOLS_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> + +#include <basegfx/range/b2drectangle.hxx> + +#include <tools.hxx> + +#include <vector> + +class MetaAction; +class GDIMetaFile; +class Graphic; + + +namespace slideshow::internal + { + /// meta file loading specialities: + enum mtf_load_flags { + /// no flags + MTF_LOAD_NONE = 0, + /// the source of the metafile might be a foreign + /// application. The metafile is checked against unsupported + /// content, and, if necessary, returned as a pre-rendered + /// bitmap. + MTF_LOAD_FOREIGN_SOURCE = 2, + /// retrieve a meta file for the page background only + MTF_LOAD_BACKGROUND_ONLY = 4, + /// retrieve the drawing layer scroll text metafile + MTF_LOAD_SCROLL_TEXT_MTF = 8 + }; + + // Animation info + // ============== + + struct MtfAnimationFrame + { + MtfAnimationFrame( const GDIMetaFileSharedPtr& rMtf, + double nDuration ) : + mpMtf( rMtf ), + mnDuration( nDuration ) + { + } + + /// Enables STL algos to be used for duration extraction + double getDuration() const + { + return mnDuration; + } + + GDIMetaFileSharedPtr mpMtf; + double mnDuration; + }; + + typedef ::std::vector< MtfAnimationFrame > VectorOfMtfAnimationFrames; + + + /** Retrieve a meta file for the given shape + + @param xShape + XShape to retrieve a metafile for. + + @param xContainingPage + The page that contains this shape. Needed for proper + import (currently, the UnoGraphicExporter needs this + information). + + */ + GDIMetaFileSharedPtr getMetaFile( const css::uno::Reference< css::lang::XComponent >& xSource, + const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage, + int mtfLoadFlags, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + /** Gets the next action offset for iterating meta actions which is most + often returns 1. + */ + sal_Int32 getNextActionOffset( MetaAction * pCurrAct ); + + /** Extract a vector of animation frames from given Graphic. + + @param o_rFrames + Resulting vector of animated metafiles + + @param o_rLoopCount + Number of times the bitmap animation shall be repeated + + @param rGraphic + Input graphic object, to extract animations from + */ + bool getAnimationFromGraphic(VectorOfMtfAnimationFrames& o_rFrames, + sal_uInt32& o_rLoopCount, + const Graphic& rGraphic); + + /** Retrieve scroll text animation rectangles from given metafile + + @return true, if both rectangles have been found, false + otherwise. + */ + bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect, + ::basegfx::B2DRectangle& o_rPaintRect, + const GDIMetaFileSharedPtr& rMtf ); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_GDIMTFTOOLS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx b/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx new file mode 100644 index 000000000..bcc353e0d --- /dev/null +++ b/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx @@ -0,0 +1,249 @@ +/* -*- 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 <subsettableshapemanager.hxx> +#include <eventqueue.hxx> +#include "intrinsicanimationactivity.hxx" +#include <intrinsicanimationeventhandler.hxx> + +#include <memory> + +namespace slideshow::internal +{ + namespace { + + /** Activity for intrinsic shape animations + + This is an Activity interface implementation for intrinsic + shape animations. Intrinsic shape animations are + animations directly within a shape, e.g. drawing layer + animations, or GIF animations. + */ + class IntrinsicAnimationActivity : public Activity + { + public: + /** Create an IntrinsicAnimationActivity. + + @param rContext + Common slideshow objects + + @param rDrawShape + Shape to control the intrinsic animation for + + @param rWakeupEvent + Externally generated wakeup event, to set this + activity to sleep during inter-frame intervals. Must + come from the outside, since wakeup event and this + object have mutual references to each other. + + @param rTimeouts + Vector of timeout values, to wait before the next + frame is shown. + */ + IntrinsicAnimationActivity( const SlideShowContext& rContext, + const DrawShapeSharedPtr& rDrawShape, + const WakeupEventSharedPtr& rWakeupEvent, + ::std::vector<double>&& rTimeouts, + ::std::size_t nNumLoops ); + IntrinsicAnimationActivity(const IntrinsicAnimationActivity&) = delete; + IntrinsicAnimationActivity& operator=(const IntrinsicAnimationActivity&) = delete; + + virtual void dispose() override; + virtual double calcTimeLag() const override; + virtual bool perform() override; + virtual bool isActive() const override; + virtual void dequeued() override; + virtual void end() override; + + bool enableAnimations(); + + private: + SlideShowContext maContext; + std::weak_ptr<DrawShape> mpDrawShape; + WakeupEventSharedPtr mpWakeupEvent; + IntrinsicAnimationEventHandlerSharedPtr mpListener; + ::std::vector<double> maTimeouts; + ::std::size_t mnCurrIndex; + ::std::size_t mnNumLoops; + ::std::size_t mnLoopCount; + bool mbIsActive; + }; + + + class IntrinsicAnimationListener : public IntrinsicAnimationEventHandler + { + public: + explicit IntrinsicAnimationListener( IntrinsicAnimationActivity& rActivity ) : + mrActivity( rActivity ) + {} + IntrinsicAnimationListener(const IntrinsicAnimationListener&) = delete; + IntrinsicAnimationListener& operator=(const IntrinsicAnimationListener&) = delete; + + private: + + virtual bool enableAnimations() override { return mrActivity.enableAnimations(); } + virtual bool disableAnimations() override { mrActivity.end(); return true; } + + IntrinsicAnimationActivity& mrActivity; + }; + + } + + IntrinsicAnimationActivity::IntrinsicAnimationActivity( const SlideShowContext& rContext, + const DrawShapeSharedPtr& rDrawShape, + const WakeupEventSharedPtr& rWakeupEvent, + ::std::vector<double>&& rTimeouts, + ::std::size_t nNumLoops ) : + maContext( rContext ), + mpDrawShape( rDrawShape ), + mpWakeupEvent( rWakeupEvent ), + mpListener( std::make_shared<IntrinsicAnimationListener>(*this) ), + maTimeouts( std::move(rTimeouts) ), + mnCurrIndex(0), + mnNumLoops(nNumLoops), + mnLoopCount(0), + mbIsActive(false) + { + ENSURE_OR_THROW( rContext.mpSubsettableShapeManager, + "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid shape manager" ); + ENSURE_OR_THROW( rDrawShape, + "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid draw shape" ); + ENSURE_OR_THROW( rWakeupEvent, + "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid wakeup event" ); + ENSURE_OR_THROW( !maTimeouts.empty(), + "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Empty timeout vector" ); + + maContext.mpSubsettableShapeManager->addIntrinsicAnimationHandler( + mpListener ); + } + + void IntrinsicAnimationActivity::dispose() + { + end(); + + if( mpWakeupEvent ) + mpWakeupEvent->dispose(); + + maContext.dispose(); + mpDrawShape.reset(); + mpWakeupEvent.reset(); + maTimeouts.clear(); + mnCurrIndex = 0; + + maContext.mpSubsettableShapeManager->removeIntrinsicAnimationHandler( + mpListener ); + } + + double IntrinsicAnimationActivity::calcTimeLag() const + { + return 0.0; + } + + bool IntrinsicAnimationActivity::perform() + { + if( !isActive() ) + return false; + + DrawShapeSharedPtr pDrawShape( mpDrawShape.lock() ); + if( !pDrawShape || !mpWakeupEvent ) + { + // event or draw shape vanished, no sense living on -> + // commit suicide. + dispose(); + return false; + } + + const ::std::size_t nNumFrames(maTimeouts.size()); + + // mnNumLoops == 0 means infinite looping + if( mnNumLoops != 0 && + mnLoopCount >= mnNumLoops ) + { + // #i55294# After finishing the loops, display the last frame + // powerpoint 2013 and firefox etc show the last frame when + // the animation ends + pDrawShape->setIntrinsicAnimationFrame(nNumFrames - 1); + maContext.mpSubsettableShapeManager->notifyShapeUpdate( pDrawShape ); + + end(); + + return false; + } + + ::std::size_t nNewIndex = 0; + + pDrawShape->setIntrinsicAnimationFrame( mnCurrIndex ); + + mpWakeupEvent->start(); + mpWakeupEvent->setNextTimeout( maTimeouts[mnCurrIndex] ); + + mnLoopCount += (mnCurrIndex + 1) / nNumFrames; + nNewIndex = (mnCurrIndex + 1) % nNumFrames; + + maContext.mrEventQueue.addEvent( mpWakeupEvent ); + maContext.mpSubsettableShapeManager->notifyShapeUpdate( pDrawShape ); + mnCurrIndex = nNewIndex; + + return false; // don't reinsert, WakeupEvent will perform + // that after the given timeout + } + + bool IntrinsicAnimationActivity::isActive() const + { + return mbIsActive; + } + + void IntrinsicAnimationActivity::dequeued() + { + // not used here + } + + void IntrinsicAnimationActivity::end() + { + // there is no dedicated end state, just become inactive: + mbIsActive = false; + } + + bool IntrinsicAnimationActivity::enableAnimations() + { + mbIsActive = true; + return maContext.mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) ); + + } + + + ActivitySharedPtr createIntrinsicAnimationActivity( + const SlideShowContext& rContext, + const DrawShapeSharedPtr& rDrawShape, + const WakeupEventSharedPtr& rWakeupEvent, + ::std::vector<double>&& rTimeouts, + sal_uInt32 nNumLoops) + { + return std::make_shared<IntrinsicAnimationActivity>(rContext, + rDrawShape, + rWakeupEvent, + std::move(rTimeouts), + nNumLoops); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/intrinsicanimationactivity.hxx b/slideshow/source/engine/shapes/intrinsicanimationactivity.hxx new file mode 100644 index 000000000..6933c7cff --- /dev/null +++ b/slideshow/source/engine/shapes/intrinsicanimationactivity.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_SHAPES_INTRINSICANIMATIONACTIVITY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_INTRINSICANIMATIONACTIVITY_HXX + +#include <wakeupevent.hxx> +#include <activity.hxx> +#include <slideshowcontext.hxx> +#include "drawshape.hxx" + +/* Definition of IntrinsicAnimationActivity class */ + +namespace slideshow::internal + { + /** Create an IntrinsicAnimationActivity. + + This is an Activity interface implementation for intrinsic + shape animations. Intrinsic shape animations are + animations directly within a shape, e.g. drawing layer + animations, or GIF animations. + + @param rContext + Common slideshow objects + + @param rDrawShape + Shape to control the intrinsic animation for + + @param rWakeupEvent + Externally generated wakeup event, to set this + activity to sleep during inter-frame intervals. Must + come from the outside, since wakeup event and this + object have mutual references to each other. + + @param rTimeouts + Vector of timeout values, to wait before the next + frame is shown. + */ + ActivitySharedPtr createIntrinsicAnimationActivity( + const SlideShowContext& rContext, + const DrawShapeSharedPtr& rDrawShape, + const WakeupEventSharedPtr& rWakeupEvent, + ::std::vector<double>&& rTimeouts, + sal_uInt32 nNumLoops); + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_INTRINSICANIMATIONACTIVITY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/mediashape.cxx b/slideshow/source/engine/shapes/mediashape.cxx new file mode 100644 index 000000000..a5cbb926f --- /dev/null +++ b/slideshow/source/engine/shapes/mediashape.cxx @@ -0,0 +1,257 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include "mediashape.hxx" +#include "viewmediashape.hxx" +#include "externalshapebase.hxx" +#include <slideshowcontext.hxx> +#include <shape.hxx> +#include <tools.hxx> + +#include <algorithm> + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + namespace { + + /** Represents a media shape. + + This implementation offers support for media shapes. + Such shapes need special treatment. + */ + class MediaShape : public ExternalShapeBase + { + public: + /** Create a shape for the given XShape for a media object + + @param xShape + The XShape to represent. + + @param nPrio + Externally-determined shape priority (used e.g. for + paint ordering). This number _must be_ unique! + */ + MediaShape( const css::uno::Reference< css::drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; + + private: + + // View layer methods + + + virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) override; + virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override; + virtual void clearAllViewLayers() override; + + + // ExternalShapeBase methods + + + virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const override; + virtual void implViewChanged( const UnoViewSharedPtr& rView ) override; + virtual void implViewsChanged() override; + virtual bool implStartIntrinsicAnimation() override; + virtual bool implEndIntrinsicAnimation() override; + virtual void implPauseIntrinsicAnimation() override; + virtual bool implIsIntrinsicAnimationPlaying() const override; + virtual void implSetIntrinsicAnimationTime(double) override; + void implSetLooping(bool bLooping) override; + + /// the list of active view shapes (one for each registered view layer) + typedef ::std::vector< ViewMediaShapeSharedPtr > ViewMediaShapeVector; + ViewMediaShapeVector maViewMediaShapes; + bool mbIsPlaying; + }; + + } + + MediaShape::MediaShape( const uno::Reference< drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext ) : + ExternalShapeBase( xShape, nPrio, rContext ), + maViewMediaShapes(), + mbIsPlaying(false) + { + } + + + void MediaShape::implViewChanged( const UnoViewSharedPtr& rView ) + { + const ::basegfx::B2DRectangle& rBounds = getBounds(); + // determine ViewMediaShape that needs update + for( const auto& pViewMediaShape : maViewMediaShapes ) + if( pViewMediaShape->getViewLayer()->isOnView( rView ) ) + pViewMediaShape->resize( rBounds ); + } + + + void MediaShape::implViewsChanged() + { + const ::basegfx::B2DRectangle& rBounds = getBounds(); + // resize all ViewShapes + for( const auto& pViewMediaShape : maViewMediaShapes ) + pViewMediaShape->resize( rBounds ); + } + + + void MediaShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) + { + maViewMediaShapes.push_back( + std::make_shared<ViewMediaShape>( rNewLayer, + getXShape(), + mxComponentContext )); + + // push new size to view shape + maViewMediaShapes.back()->resize( getBounds() ); + + // render the Shape on the newly added ViewLayer + if( bRedrawLayer ) + maViewMediaShapes.back()->render( getBounds() ); + } + + + bool MediaShape::removeViewLayer( const ViewLayerSharedPtr& rLayer ) + { + const ViewMediaShapeVector::iterator aEnd( maViewMediaShapes.end() ); + + OSL_ENSURE( ::std::count_if(maViewMediaShapes.begin(), + aEnd, + [&rLayer] + ( const ViewMediaShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) < 2, + "MediaShape::removeViewLayer(): Duplicate ViewLayer entries!" ); + + ViewMediaShapeVector::iterator aIter; + + if( (aIter=::std::remove_if( maViewMediaShapes.begin(), + aEnd, + [&rLayer] + ( const ViewMediaShapeSharedPtr& pShape ) + { return rLayer == pShape->getViewLayer(); } ) ) == aEnd ) + { + // view layer seemingly was not added, failed + return false; + } + + // actually erase from container + maViewMediaShapes.erase( aIter, aEnd ); + + return true; + } + + + void MediaShape::clearAllViewLayers() + { + maViewMediaShapes.clear(); + } + + + bool MediaShape::implRender( const ::basegfx::B2DRange& rCurrBounds ) const + { + // redraw all view shapes, by calling their update() method + if( o3tl::make_unsigned(::std::count_if( maViewMediaShapes.begin(), + maViewMediaShapes.end(), + [&rCurrBounds] + ( const ViewMediaShapeSharedPtr& pShape ) + { return pShape->render( rCurrBounds ); } )) + != maViewMediaShapes.size() ) + { + // at least one of the ViewShape::update() calls did return + // false - update failed on at least one ViewLayer + return false; + } + + return true; + } + + + bool MediaShape::implStartIntrinsicAnimation() + { + for( const auto& pViewMediaShape : maViewMediaShapes ) + pViewMediaShape->startMedia(); + + mbIsPlaying = true; + + return true; + } + + + bool MediaShape::implEndIntrinsicAnimation() + { + for( const auto& pViewMediaShape : maViewMediaShapes ) + pViewMediaShape->endMedia(); + + mbIsPlaying = false; + + return true; + } + + + void MediaShape::implPauseIntrinsicAnimation() + { + for( const auto& pViewMediaShape : maViewMediaShapes ) + pViewMediaShape->pauseMedia(); + + mbIsPlaying = false; + } + + + bool MediaShape::implIsIntrinsicAnimationPlaying() const + { + return mbIsPlaying; + } + + + void MediaShape::implSetIntrinsicAnimationTime(double fTime) + { + for( const auto& pViewMediaShape : maViewMediaShapes ) + pViewMediaShape->setMediaTime( fTime ); + } + + void MediaShape::implSetLooping(bool bLooping) + { + for (const auto& pViewMediaShape : maViewMediaShapes) + { + pViewMediaShape->setLooping(bLooping); + } + } + + ShapeSharedPtr createMediaShape( + const uno::Reference< drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext) + { + return std::make_shared<MediaShape>(xShape, nPrio, rContext); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/mediashape.hxx b/slideshow/source/engine/shapes/mediashape.hxx new file mode 100644 index 000000000..4b2a542ee --- /dev/null +++ b/slideshow/source/engine/shapes/mediashape.hxx @@ -0,0 +1,44 @@ +/* -*- 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_SHAPES_MEDIASHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_MEDIASHAPE_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <memory> + + +namespace com::sun::star::drawing { class XShape; } + +namespace slideshow::internal +{ + struct SlideShowContext; + class Shape; + typedef ::std::shared_ptr< Shape > ShapeSharedPtr; + + ShapeSharedPtr createMediaShape( + const css::uno::Reference<css::drawing::XShape >& xShape, + double nPrio, + const SlideShowContext& rContext); + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_MEDIASHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/shapeimporter.cxx b/slideshow/source/engine/shapes/shapeimporter.cxx new file mode 100644 index 000000000..d896caa55 --- /dev/null +++ b/slideshow/source/engine/shapes/shapeimporter.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <vcl/GraphicObject.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <cppcanvas/basegfxfactory.hxx> +#include <cppcanvas/polypolygon.hxx> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/drawing/ColorMode.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <com/sun/star/drawing/XLayerSupplier.hpp> +#include <com/sun/star/drawing/XLayerManager.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> + +#include "drawshape.hxx" +#include "backgroundshape.hxx" +#include "mediashape.hxx" +#include "appletshape.hxx" +#include <shapeimporter.hxx> +#include <slideshowexceptions.hxx> +#include <tools.hxx> +#include <slideshowcontext.hxx> +#include <unoviewcontainer.hxx> + +#include <memory> + +using namespace com::sun::star; + +namespace slideshow::internal { + +namespace { + +std::unique_ptr<GraphicObject> importShapeGraphic(uno::Reference<beans::XPropertySet> const& xPropSet) +{ + std::unique_ptr<GraphicObject> xRet; + + uno::Reference<graphic::XGraphic> xGraphic; + if (!getPropertyValue(xGraphic, xPropSet, "Graphic") || !xGraphic.is()) + { + // no or empty property - cannot import shape graphic + return xRet; + } + + Graphic aGraphic(xGraphic); + xRet.reset(new GraphicObject(aGraphic)); + + if (GraphicType::Default == xRet->GetType() || GraphicType::NONE == xRet->GetType()) + { + xRet.reset(); + } + return xRet; +} + +/** This shape implementation just acts as a dummy for the layermanager. + Its sole role is for hit test detection of group shapes. +*/ +class ShapeOfGroup : public Shape +{ +public: + ShapeOfGroup( ShapeSharedPtr const& pGroupShape, + uno::Reference<drawing::XShape> const& xShape, + uno::Reference<beans::XPropertySet> const& xPropSet, + double nPrio ); + + // Shape: + virtual uno::Reference<drawing::XShape> getXShape() const override; + virtual void addViewLayer( ViewLayerSharedPtr const& pNewLayer, + bool bRedrawLayer ) override; + virtual bool removeViewLayer( ViewLayerSharedPtr const& pNewLayer ) override; + virtual void clearAllViewLayers() override; + virtual bool update() const override; + virtual bool render() const override; + virtual bool isContentChanged() const override; + virtual basegfx::B2DRectangle getBounds() const override; + virtual basegfx::B2DRectangle getDomBounds() const override; + virtual basegfx::B2DRectangle getUpdateArea() const override; + virtual bool isVisible() const override; + virtual double getPriority() const override; + virtual bool isBackgroundDetached() const override; + +private: + ShapeSharedPtr const mpGroupShape; + uno::Reference<drawing::XShape> const mxShape; + double const mnPrio; + basegfx::B2DPoint maPosOffset; + double mnWidth; + double mnHeight; +}; + +ShapeOfGroup::ShapeOfGroup( ShapeSharedPtr const& pGroupShape, + uno::Reference<drawing::XShape> const& xShape, + uno::Reference<beans::XPropertySet> const& xPropSet, + double nPrio ) : + mpGroupShape(pGroupShape), + mxShape(xShape), + mnPrio(nPrio) +{ + // read bound rect + uno::Any const aTmpRect_( xPropSet->getPropertyValue( "BoundRect" )); + awt::Rectangle const aTmpRect( aTmpRect_.get<awt::Rectangle>() ); + basegfx::B2DRectangle const groupPosSize( pGroupShape->getBounds() ); + maPosOffset = basegfx::B2DPoint( aTmpRect.X - groupPosSize.getMinX(), + aTmpRect.Y - groupPosSize.getMinY() ); + mnWidth = aTmpRect.Width; + mnHeight = aTmpRect.Height; +} + +uno::Reference<drawing::XShape> ShapeOfGroup::getXShape() const +{ + return mxShape; +} + +void ShapeOfGroup::addViewLayer( ViewLayerSharedPtr const& /*pNewLayer*/, + bool /*bRedrawLayer*/ ) +{ +} + +bool ShapeOfGroup::removeViewLayer( ViewLayerSharedPtr const& /*pNewLayer*/ ) +{ + return true; +} + +void ShapeOfGroup::clearAllViewLayers() +{ +} + +bool ShapeOfGroup::update() const +{ + return true; +} + +bool ShapeOfGroup::render() const +{ + return true; +} + +bool ShapeOfGroup::isContentChanged() const +{ + return false; +} + +basegfx::B2DRectangle ShapeOfGroup::getBounds() const +{ + basegfx::B2DRectangle const groupPosSize( mpGroupShape->getBounds() ); + double const posX = groupPosSize.getMinX() + maPosOffset.getX(); + double const posY = groupPosSize.getMinY() + maPosOffset.getY(); + return basegfx::B2DRectangle( posX, posY, posX + mnWidth, posY + mnHeight ); +} + +basegfx::B2DRectangle ShapeOfGroup::getDomBounds() const +{ + return getBounds(); +} + +basegfx::B2DRectangle ShapeOfGroup::getUpdateArea() const +{ + return getBounds(); +} + +bool ShapeOfGroup::isVisible() const +{ + return mpGroupShape->isVisible(); +} + +double ShapeOfGroup::getPriority() const +{ + return mnPrio; +} + +bool ShapeOfGroup::isBackgroundDetached() const +{ + return false; +} + +} // anon namespace + +ShapeSharedPtr ShapeImporter::createShape( + uno::Reference<drawing::XShape> const& xCurrShape, + uno::Reference<beans::XPropertySet> const& xPropSet, + std::u16string_view shapeType ) const +{ + if( shapeType == u"com.sun.star.drawing.MediaShape" || shapeType == u"com.sun.star.presentation.MediaShape" ) + { + // Media shape (video etc.). This is a special object + return createMediaShape(xCurrShape, + mnAscendingPrio, + mrContext); + } + else if( shapeType == u"com.sun.star.drawing.AppletShape" ) + { + // PropertyValues to copy from XShape to applet + static const char* aPropertyValues[] = + { + "AppletCodeBase", + "AppletName", + "AppletCode", + "AppletCommands", + "AppletIsScript" + }; + + // (Java)Applet shape. This is a special object + return createAppletShape( xCurrShape, + mnAscendingPrio, + "com.sun.star.comp.sfx2.AppletObject", + aPropertyValues, + SAL_N_ELEMENTS(aPropertyValues), + mrContext ); + } + else if( shapeType == u"com.sun.star.drawing.OLE2Shape" || shapeType == u"com.sun.star.presentation.OLE2Shape" ) + { + // #i46224# Mark OLE shapes as foreign content - scan them for + // unsupported actions, and fallback to bitmap, if necessary + return DrawShape::create( xCurrShape, + mxPage, + mnAscendingPrio, + true, + mrContext ); + } + else if( shapeType == u"com.sun.star.drawing.GraphicObjectShape" || shapeType == u"com.sun.star.presentation.GraphicObjectShape" ) + { + // to get hold of GIF animations, inspect Graphic + // objects more thoroughly (the plain-jane shape + // metafile of course would only contain the first + // animation frame) + std::unique_ptr<GraphicObject> xGraphicObject(importShapeGraphic(xPropSet)); + if (!xGraphicObject) + return ShapeSharedPtr(); // error loading graphic - + // no placeholders in + // slideshow + + if (!xGraphicObject->IsAnimated()) + { + // no animation - simply utilize plain draw shape import + + // import shape as bitmap - either it's a bitmap + // anyway, or it's a metafile, which currently the + // metafile renderer might not display correctly. + return DrawShape::create( xCurrShape, + mxPage, + mnAscendingPrio, + true, + mrContext ); + } + + + // now extract relevant shape attributes via API + + + drawing::ColorMode eColorMode( drawing::ColorMode_STANDARD ); + sal_Int16 nLuminance(0); + sal_Int16 nContrast(0); + sal_Int16 nRed(0); + sal_Int16 nGreen(0); + sal_Int16 nBlue(0); + double nGamma(1.0); + sal_Int16 nTransparency(0); + sal_Int32 nRotation(0); + + getPropertyValue( eColorMode, xPropSet, "GraphicColorMode" ); + getPropertyValue( nLuminance, xPropSet, "AdjustLuminance" ); + getPropertyValue( nContrast, xPropSet, "AdjustContrast" ); + getPropertyValue( nRed, xPropSet, "AdjustRed" ); + getPropertyValue( nGreen, xPropSet, "AdjustGreen" ); + getPropertyValue( nBlue, xPropSet, "AdjustBlue" ); + getPropertyValue( nGamma, xPropSet, "Gamma" ); + getPropertyValue( nTransparency, xPropSet, "Transparency" ); + getPropertyValue( nRotation, xPropSet, "RotateAngle" ); + + GraphicAttr aGraphAttrs; + aGraphAttrs.SetDrawMode( static_cast<GraphicDrawMode>(eColorMode) ); + aGraphAttrs.SetLuminance( nLuminance ); + aGraphAttrs.SetContrast( nContrast ); + aGraphAttrs.SetChannelR( nRed ); + aGraphAttrs.SetChannelG( nGreen ); + aGraphAttrs.SetChannelB( nBlue ); + aGraphAttrs.SetGamma( nGamma ); + aGraphAttrs.SetAlpha( 255 - static_cast<sal_uInt8>(nTransparency) ); + aGraphAttrs.SetRotation( Degree10(static_cast<sal_Int16>(nRotation*10)) ); + + text::GraphicCrop aGraphCrop; + if( getPropertyValue( aGraphCrop, xPropSet, "GraphicCrop" )) + { + aGraphAttrs.SetCrop( aGraphCrop.Left, + aGraphCrop.Top, + aGraphCrop.Right, + aGraphCrop.Bottom ); + } + + // fetch readily transformed and color-modified + // graphic + + + Graphic aGraphic( + xGraphicObject->GetTransformedGraphic( + xGraphicObject->GetPrefSize(), + xGraphicObject->GetPrefMapMode(), + aGraphAttrs ) ); + + return DrawShape::create( xCurrShape, + mxPage, + mnAscendingPrio, + aGraphic, + mrContext ); + } + else + { + return DrawShape::create( xCurrShape, + mxPage, + mnAscendingPrio, + false, + mrContext ); + } +} + +bool ShapeImporter::isSkip( + uno::Reference<beans::XPropertySet> const& xPropSet, + std::u16string_view shapeType, + uno::Reference< drawing::XLayer> const& xLayer ) +{ + // skip empty presentation objects: + bool bEmpty = false; + if( getPropertyValue( bEmpty, + xPropSet, + "IsEmptyPresentationObject") && + bEmpty ) + { + return true; + } + + //skip shapes which corresponds to annotations + if(xLayer.is()) + { + OUString layerName; + const uno::Any& a(xLayer->getPropertyValue("Name") ); + bool const bRet = (a >>= layerName); + if(bRet) + { + if( layerName == "DrawnInSlideshow" ) + { + //Transform shapes into PolyPolygons + importPolygons(xPropSet); + + return true; + } + } + } + + // don't export presentation placeholders on masterpage + // they can be non empty when user edits the default texts + if(mbConvertingMasterPage) + { + if( shapeType == u"com.sun.star.presentation.TitleTextShape" || shapeType == u"com.sun.star.presentation.OutlinerShape" ) + { + return true; + } + } + return false; +} + + +void ShapeImporter::importPolygons(uno::Reference<beans::XPropertySet> const& xPropSet) { + + drawing::PointSequenceSequence aRetval; + sal_Int32 nLineColor=0; + double fLineWidth; + getPropertyValue( aRetval, xPropSet, "PolyPolygon" ); + getPropertyValue( nLineColor, xPropSet, "LineColor" ); + getPropertyValue( fLineWidth, xPropSet, "LineWidth" ); + + const drawing::PointSequence* pOuterSequence = aRetval.getArray(); + + ::basegfx::B2DPolygon aPoly; + basegfx::B2DPoint aPoint; + for( const awt::Point& rPoint : *pOuterSequence ) + { + aPoint.setX(rPoint.X); + aPoint.setY(rPoint.Y); + aPoly.append( aPoint ); + } + for( const auto& pView : mrContext.mrViewContainer ) + { + ::cppcanvas::PolyPolygonSharedPtr pPolyPoly( + ::cppcanvas::BaseGfxFactory::createPolyPolygon( pView->getCanvas(), + aPoly ) ); + if( pPolyPoly ) + { + pPolyPoly->setRGBALineColor( unoColor2RGBColor( nLineColor ).getIntegerColor() ); + pPolyPoly->setStrokeWidth(fLineWidth); + pPolyPoly->draw(); + maPolygons.push_back(pPolyPoly); + } + } +} + +ShapeSharedPtr ShapeImporter::importBackgroundShape() // throw (ShapeLoadFailedException) +{ + if( maShapesStack.empty() ) + throw ShapeLoadFailedException(); + + XShapesEntry& rTop = maShapesStack.top(); + ShapeSharedPtr pBgShape( + createBackgroundShape(mxPage, + uno::Reference<drawing::XDrawPage>( + rTop.mxShapes, + uno::UNO_QUERY_THROW), + mrContext) ); + mnAscendingPrio += 1.0; + + return pBgShape; +} + +ShapeSharedPtr ShapeImporter::importShape() // throw (ShapeLoadFailedException) +{ + ShapeSharedPtr pRet; + bool bIsGroupShape = false; + + while( !maShapesStack.empty() && !pRet ) + { + XShapesEntry& rTop = maShapesStack.top(); + if( rTop.mnPos < rTop.mnCount ) + { + uno::Reference<drawing::XShape> const xCurrShape( + rTop.mxShapes->getByIndex( rTop.mnPos ), uno::UNO_QUERY ); + ++rTop.mnPos; + uno::Reference<beans::XPropertySet> xPropSet( + xCurrShape, uno::UNO_QUERY ); + if( !xPropSet.is() ) + { + // we definitely need the properties of + // the shape here. This will also fail, + // if getByIndex did not return a valid + // shape + throw ShapeLoadFailedException(); + } + + //Retrieve the layer for the current shape + uno::Reference< drawing::XLayer > xDrawnInSlideshow; + + uno::Reference< drawing::XLayerSupplier > xLayerSupplier(mxPagesSupplier, uno::UNO_QUERY); + if(xLayerSupplier.is()) + { + uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager(); + + uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY); + + xDrawnInSlideshow = xLayerManager->getLayerForShape(xCurrShape); + } + + OUString const shapeType( xCurrShape->getShapeType()); + + // is this shape presentation-invisible? + if( !isSkip(xPropSet, shapeType, xDrawnInSlideshow) ) + { + bIsGroupShape = shapeType == "com.sun.star.drawing.GroupShape"; + + if( rTop.mpGroupShape ) // in group particle mode? + { + pRet = std::make_shared<ShapeOfGroup>( + rTop.mpGroupShape /* container shape */, + xCurrShape, xPropSet, + mnAscendingPrio ); + } + else + { + pRet = createShape( xCurrShape, xPropSet, shapeType ); + } + mnAscendingPrio += 1.0; + } + } + if( rTop.mnPos >= rTop.mnCount ) + { + // group or top-level shapes finished: + maShapesStack.pop(); + } + if( bIsGroupShape && pRet ) + { + // push new group on the stack: group traversal + maShapesStack.push( XShapesEntry( pRet ) ); + } + } + + return pRet; +} + +bool ShapeImporter::isImportDone() const +{ + return maShapesStack.empty(); +} + +const PolyPolygonVector& ShapeImporter::getPolygons() const +{ + return maPolygons; +} + +ShapeImporter::ShapeImporter( uno::Reference<drawing::XDrawPage> const& xPage, + uno::Reference<drawing::XDrawPage> const& xActualPage, + uno::Reference<drawing::XDrawPagesSupplier> const& xPagesSupplier, + const SlideShowContext& rContext, + sal_Int32 nOrdNumStart, + bool bConvertingMasterPage ) : + mxPage( xActualPage ), + mxPagesSupplier( xPagesSupplier ), + mrContext( rContext ), + maPolygons(), + maShapesStack(), + mnAscendingPrio( nOrdNumStart ), + mbConvertingMasterPage( bConvertingMasterPage ) +{ + uno::Reference<drawing::XShapes> const xShapes( + xPage, uno::UNO_QUERY_THROW ); + maShapesStack.push( XShapesEntry(xShapes) ); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewappletshape.cxx b/slideshow/source/engine/shapes/viewappletshape.cxx new file mode 100644 index 000000000..736cb9d94 --- /dev/null +++ b/slideshow/source/engine/shapes/viewappletshape.cxx @@ -0,0 +1,258 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/utils/canvastools.hxx> + +#include <cppcanvas/canvas.hxx> +#include <canvas/canvastools.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/awt/WindowDescriptor.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/awt/WindowAttribute.hpp> +#include <com/sun/star/awt/VclWindowPeerAttribute.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/frame/Frame.hpp> +#include <com/sun/star/frame/XSynchronousFrameLoader.hpp> + +#include "viewappletshape.hxx" +#include <tools.hxx> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + ViewAppletShape::ViewAppletShape( const ViewLayerSharedPtr& rViewLayer, + const uno::Reference< drawing::XShape >& rxShape, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const uno::Reference< uno::XComponentContext >& rxContext ) : + mpViewLayer( rViewLayer ), + mxViewer(), + mxFrame(), + mxComponentContext( rxContext ) + { + ENSURE_OR_THROW( rxShape.is(), "ViewAppletShape::ViewAppletShape(): Invalid Shape" ); + ENSURE_OR_THROW( mpViewLayer, "ViewAppletShape::ViewAppletShape(): Invalid View" ); + ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewAppletShape::ViewAppletShape(): Invalid ViewLayer canvas" ); + ENSURE_OR_THROW( mxComponentContext.is(), "ViewAppletShape::ViewAppletShape(): Invalid component context" ); + + uno::Reference<lang::XMultiComponentFactory> xFactory( + mxComponentContext->getServiceManager(), + uno::UNO_SET_THROW ); + + mxViewer.set( xFactory->createInstanceWithContext( rServiceName, + mxComponentContext), + uno::UNO_QUERY_THROW ); + + uno::Reference< beans::XPropertySet > xShapePropSet( rxShape, + uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xViewerPropSet( mxViewer, + uno::UNO_QUERY_THROW ); + + // copy shape properties to applet viewer + OUString aPropName; + for( std::size_t i=0; i<nNumPropEntries; ++i ) + { + aPropName = OUString::createFromAscii( pPropCopyTable[i] ); + xViewerPropSet->setPropertyValue( aPropName, + xShapePropSet->getPropertyValue( + aPropName )); + } + } + + ViewAppletShape::~ViewAppletShape() + { + try + { + endApplet(); + } + catch (const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + } + + const ViewLayerSharedPtr& ViewAppletShape::getViewLayer() const + { + return mpViewLayer; + } + + void ViewAppletShape::startApplet( const ::basegfx::B2DRectangle& rBounds ) + { + ENSURE_OR_RETURN_VOID( mpViewLayer && mpViewLayer->getCanvas() && mpViewLayer->getCanvas()->getUNOCanvas().is(), + "ViewAppletShape::startApplet(): Invalid or disposed view" ); + try + { + ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas(); + + uno::Reference< beans::XPropertySet > xPropSet( pCanvas->getUNOCanvas()->getDevice(), + uno::UNO_QUERY_THROW ); + + uno::Reference< awt::XWindow2 > xParentWindow( + xPropSet->getPropertyValue("Window"), + uno::UNO_QUERY_THROW ); + + uno::Reference<lang::XMultiComponentFactory> xFactory( + mxComponentContext->getServiceManager() ); + + if( xFactory.is() ) + { + // create an awt window to contain the applet + // ========================================== + + uno::Reference< awt::XToolkit2 > xToolkit = awt::Toolkit::create(mxComponentContext); + + awt::WindowDescriptor aOwnWinDescriptor( awt::WindowClass_SIMPLE, + OUString(), + uno::Reference< awt::XWindowPeer >(xParentWindow, + uno::UNO_QUERY_THROW), + 0, + awt::Rectangle(), + awt::WindowAttribute::SHOW + | awt::VclWindowPeerAttribute::CLIPCHILDREN ); + + uno::Reference< awt::XWindowPeer > xNewWinPeer( + xToolkit->createWindow( aOwnWinDescriptor )); + uno::Reference< awt::XWindow > xOwnWindow( xNewWinPeer, + uno::UNO_QUERY_THROW ); + + + // create a frame, and load the applet into it + // =========================================== + + mxFrame = frame::Frame::create( mxComponentContext ); + mxFrame->initialize( xOwnWindow ); + + uno::Reference < frame::XSynchronousFrameLoader > xLoader( mxViewer, + uno::UNO_SET_THROW ); + xLoader->load( uno::Sequence < beans::PropertyValue >(), + uno::Reference<frame::XFrame>(mxFrame, uno::UNO_QUERY_THROW) ); + + + // resize surrounding window and applet to current shape size + // ========================================================== + + ::basegfx::B2DRange aTmpRange; + ::canvas::tools::calcTransformedRectBounds( aTmpRange, + rBounds, + mpViewLayer->getTransformation() ); + const ::basegfx::B2IRange& rPixelBounds( + ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange )); + + uno::Reference< awt::XWindow > xSurroundingWindow( mxFrame->getContainerWindow() ); + if( xSurroundingWindow.is() ) + xSurroundingWindow->setPosSize( rPixelBounds.getMinX(), + rPixelBounds.getMinY(), + static_cast<sal_Int32>(rPixelBounds.getWidth()), + static_cast<sal_Int32>(rPixelBounds.getHeight()), + awt::PosSize::POSSIZE ); + + uno::Reference< awt::XWindow > xAppletWindow( mxFrame->getComponentWindow() ); + if( xAppletWindow.is() ) + xAppletWindow->setPosSize( 0, 0, + static_cast<sal_Int32>(rPixelBounds.getWidth()), + static_cast<sal_Int32>(rPixelBounds.getHeight()), + awt::PosSize::POSSIZE ); + } + } + catch (uno::Exception &) + { + } + } + + + void ViewAppletShape::endApplet() + { + uno::Reference<util::XCloseable> xCloseable( + mxFrame, + uno::UNO_QUERY ); + + if( xCloseable.is() ) + { + xCloseable->close( true ); + mxFrame.clear(); + } + } + + + bool ViewAppletShape::render( const ::basegfx::B2DRectangle& rBounds ) const + { + ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas(); + + if( !pCanvas ) + return false; + + if( !mxFrame.is() ) + { + // fill the shape background with black + fillRect( pCanvas, + rBounds, + 0xFFFFFFFFU ); + } + + return true; + } + + bool ViewAppletShape::resize( const ::basegfx::B2DRectangle& rBounds ) const + { + if( !mxFrame.is() ) + return false; + + ::basegfx::B2DRange aTmpRange; + ::canvas::tools::calcTransformedRectBounds( aTmpRange, + rBounds, + mpViewLayer->getTransformation() ); + const ::basegfx::B2IRange& rPixelBounds( + ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange )); + + uno::Reference< awt::XWindow > xFrameWindow( mxFrame->getContainerWindow() ); + if( xFrameWindow.is() ) + xFrameWindow->setPosSize( rPixelBounds.getMinX(), + rPixelBounds.getMinY(), + static_cast<sal_Int32>(rPixelBounds.getWidth()), + static_cast<sal_Int32>(rPixelBounds.getHeight()), + awt::PosSize::POSSIZE ); + + uno::Reference< awt::XWindow > xAppletWindow( mxFrame->getComponentWindow() ); + if( xAppletWindow.is() ) + xAppletWindow->setPosSize( 0, 0, + static_cast<sal_Int32>(rPixelBounds.getWidth()), + static_cast<sal_Int32>(rPixelBounds.getHeight()), + awt::PosSize::POSSIZE ); + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewappletshape.hxx b/slideshow/source/engine/shapes/viewappletshape.hxx new file mode 100644 index 000000000..5d1b30743 --- /dev/null +++ b/slideshow/source/engine/shapes/viewappletshape.hxx @@ -0,0 +1,164 @@ +/* -*- 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_SHAPES_VIEWAPPLETSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWAPPLETSHAPE_HXX + +#include <basegfx/range/b2drectangle.hxx> +#include <com/sun/star/frame/XSynchronousFrameLoader.hpp> + +#include <memory> + +#include <viewlayer.hxx> + +namespace com::sun::star { + namespace frame { + class XSynchronousFrameLoader; + class XFrame2; + } + namespace uno { + class XComponentContext; + } + namespace drawing { + class XShape; + } +} + +namespace slideshow +{ + namespace internal + { + /** This class is the viewable representation of a draw + document's applet object, associated to a specific View + + The class is able to render the associated applet on View + implementations. + */ + class ViewAppletShape final + { + public: + /** Create a ViewAppletShape for the given View + + @param rViewLayer + The associated View object. + + @param rxShape + The associated Shape + + @param rServiceName + The service name to use, when actually creating the + viewer component + + @param pPropCopyTable + Table of plain ASCII property names, to copy from + xShape to applet. + + @param nNumPropEntries + Number of property table entries (in pPropCopyTable) + */ + ViewAppletShape( const ViewLayerSharedPtr& rViewLayer, + const css::uno::Reference< css::drawing::XShape >& rxShape, + const OUString& rServiceName, + const char** pPropCopyTable, + std::size_t nNumPropEntries, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + /** destroy the object + */ + ~ViewAppletShape(); + + /// Forbid copy construction + ViewAppletShape(const ViewAppletShape&) = delete; + /// Forbid copy assignment + ViewAppletShape& operator=(const ViewAppletShape&) = delete; + + /** Query the associated view layer of this shape + */ + const ViewLayerSharedPtr& getViewLayer() const; + + // animation methods + + + /** Notify the ViewShape that an animation starts now + + This method enters animation mode on the associate + target view. The shape can be animated in parallel on + different views. + + @param rBounds + The current applet shape bounds + */ + void startApplet( const ::basegfx::B2DRectangle& rBounds ); + + /** Notify the ViewShape that it is no longer animated + + This methods ends animation mode on the associate + target view + */ + void endApplet(); + + // render methods + + + /** Render the ViewShape + + This method renders the ViewAppletShape on the associated view. + + @param rBounds + The current applet shape bounds + + @return whether the rendering finished successfully. + */ + bool render( const ::basegfx::B2DRectangle& rBounds ) const; + + /** Resize the ViewShape + + This method resizes the ViewAppletShape on the + associated view. It does not render. + + @param rBounds + The current applet shape bounds + + @return whether the resize finished successfully. + */ + bool resize( const ::basegfx::B2DRectangle& rBounds ) const; + + private: + + ViewLayerSharedPtr mpViewLayer; + + /// the actual viewer component for this applet + css::uno::Reference< + css::frame::XSynchronousFrameLoader> mxViewer; + + /// the frame containing the applet + css::uno::Reference< + css::frame::XFrame2> mxFrame; + css::uno::Reference< + css::uno::XComponentContext> mxComponentContext; + }; + + typedef ::std::shared_ptr< ViewAppletShape > ViewAppletShapeSharedPtr; + + } +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWAPPLETSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewbackgroundshape.cxx b/slideshow/source/engine/shapes/viewbackgroundshape.cxx new file mode 100644 index 000000000..e0ca333ad --- /dev/null +++ b/slideshow/source/engine/shapes/viewbackgroundshape.cxx @@ -0,0 +1,188 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <sal/log.hxx> + +#include "viewbackgroundshape.hxx" +#include <tools.hxx> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <canvas/canvastools.hxx> +#include <cppcanvas/vclfactory.hxx> +#include <cppcanvas/basegfxfactory.hxx> +#include <cppcanvas/renderer.hxx> +#include <cppcanvas/bitmap.hxx> + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + + bool ViewBackgroundShape::prefetch( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf ) const + { + SAL_INFO( "slideshow", "::presentation::internal::ViewBackgroundShape::prefetch()" ); + ENSURE_OR_RETURN_FALSE( rMtf, + "ViewBackgroundShape::prefetch(): no valid metafile!" ); + + const ::basegfx::B2DHomMatrix& rCanvasTransform( + mpViewLayer->getTransformation() ); + + if( !mxBitmap.is() || + rMtf != mpLastMtf || + rCanvasTransform != maLastTransformation ) + { + // buffered bitmap is invalid, re-create + + // determine transformed page bounds + ::basegfx::B2DRectangle aTmpRect; + ::canvas::tools::calcTransformedRectBounds( aTmpRect, + maBounds, + rCanvasTransform ); + + // determine pixel size of bitmap (choose it one pixel + // larger, as polygon rendering takes one pixel more + // to the right and to the bottom) + const ::basegfx::B2ISize aBmpSizePixel( + ::basegfx::fround( aTmpRect.getRange().getX() + 1), + ::basegfx::fround( aTmpRect.getRange().getY() + 1) ); + + // create a bitmap of appropriate size + ::cppcanvas::BitmapSharedPtr pBitmap( + ::cppcanvas::BaseGfxFactory::createBitmap( + rDestinationCanvas, + aBmpSizePixel ) ); + + ENSURE_OR_THROW( pBitmap, + "ViewBackgroundShape::prefetch(): Cannot create background bitmap" ); + + ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( pBitmap->getBitmapCanvas() ); + + ENSURE_OR_THROW( pBitmapCanvas, + "ViewBackgroundShape::prefetch(): Cannot create background bitmap canvas" ); + + // clear bitmap + initSlideBackground( pBitmapCanvas, + aBmpSizePixel ); + + // apply linear part of destination canvas transformation (linear means in this context: + // transformation without any translational components) + ::basegfx::B2DHomMatrix aLinearTransform( rCanvasTransform ); + aLinearTransform.set( 0, 2, 0.0 ); + aLinearTransform.set( 1, 2, 0.0 ); + pBitmapCanvas->setTransformation( aLinearTransform ); + + const basegfx::B2DHomMatrix aShapeTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + maBounds.getWidth(), maBounds.getHeight(), + maBounds.getMinX(), maBounds.getMinY())); + + ::cppcanvas::RendererSharedPtr pRenderer( + ::cppcanvas::VCLFactory::createRenderer( + pBitmapCanvas, + *rMtf, + ::cppcanvas::Renderer::Parameters() ) ); + + ENSURE_OR_RETURN_FALSE( pRenderer, + "ViewBackgroundShape::prefetch(): Could not create Renderer" ); + + pRenderer->setTransformation( aShapeTransform ); + pRenderer->draw(); + + mxBitmap = pBitmap->getUNOBitmap(); + } + + mpLastMtf = rMtf; + maLastTransformation = rCanvasTransform; + + return mxBitmap.is(); + } + + ViewBackgroundShape::ViewBackgroundShape( const ViewLayerSharedPtr& rViewLayer, + const ::basegfx::B2DRectangle& rShapeBounds ) : + mpViewLayer( rViewLayer ), + mxBitmap(), + mpLastMtf(), + maLastTransformation(), + maBounds( rShapeBounds ) + { + ENSURE_OR_THROW( mpViewLayer, "ViewBackgroundShape::ViewBackgroundShape(): Invalid View" ); + ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewBackgroundShape::ViewBackgroundShape(): Invalid ViewLayer canvas" ); + } + + const ViewLayerSharedPtr& ViewBackgroundShape::getViewLayer() const + { + return mpViewLayer; + } + + bool ViewBackgroundShape::render( const GDIMetaFileSharedPtr& rMtf ) const + { + SAL_INFO( "slideshow", "::presentation::internal::ViewBackgroundShape::draw()" ); + + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas( mpViewLayer->getCanvas() ); + + if( !prefetch( rDestinationCanvas, rMtf ) ) + return false; + + ENSURE_OR_RETURN_FALSE( mxBitmap.is(), + "ViewBackgroundShape::draw(): Invalid background bitmap" ); + + ::basegfx::B2DHomMatrix aTransform( mpViewLayer->getTransformation() ); + + // invert the linear part of the view transformation + // (i.e. the view transformation without translational + // components), to be able to leave the canvas + // transformation intact (would otherwise destroy possible + // clippings, as the clip polygon is relative to the view + // coordinate system). + aTransform.set(0,2, 0.0 ); + aTransform.set(1,2, 0.0 ); + aTransform.invert(); + + rendering::RenderState aRenderState; + ::canvas::tools::initRenderState( aRenderState ); + + ::canvas::tools::setRenderStateTransform( aRenderState, aTransform ); + + try + { + rDestinationCanvas->getUNOCanvas()->drawBitmap( mxBitmap, + rDestinationCanvas->getViewState(), + aRenderState ); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + return false; + } + + return true; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewbackgroundshape.hxx b/slideshow/source/engine/shapes/viewbackgroundshape.hxx new file mode 100644 index 000000000..0f5b29646 --- /dev/null +++ b/slideshow/source/engine/shapes/viewbackgroundshape.hxx @@ -0,0 +1,96 @@ +/* -*- 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_SHAPES_VIEWBACKGROUNDSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWBACKGROUNDSHAPE_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/rendering/XBitmap.hpp> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <memory> + +#include <tools.hxx> +#include <viewlayer.hxx> + + +namespace slideshow::internal + { + /** This class is the viewable representation of a draw + document's background, associated to a specific View + + The class is able to render the associated background on + View implementations. + */ + class ViewBackgroundShape + { + public: + /** Create a ViewBackgroundShape for the given View + + @param rView + The associated View object. + + @param rShapeBounds + Bounds of the background shape, in document coordinate + system. + */ + ViewBackgroundShape( const ViewLayerSharedPtr& rViewLayer, + const ::basegfx::B2DRectangle& rShapeBounds ); + /// Forbid copy construction + ViewBackgroundShape(const ViewBackgroundShape&) = delete; + /// Forbid copy assignment + ViewBackgroundShape& operator=(const ViewBackgroundShape&) = delete; + + /** Query the associated view layer of this shape + */ + const ViewLayerSharedPtr& getViewLayer() const; + + bool render( const GDIMetaFileSharedPtr& rMtf ) const; + + private: + /** Prefetch bitmap for given canvas + */ + bool prefetch( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf ) const; + + /** The view layer this object is part of. + */ + ViewLayerSharedPtr mpViewLayer; + + /// Generated content bitmap, already with correct output size + mutable css::uno::Reference< css::rendering::XBitmap > mxBitmap; + + /// The last metafile a render object was generated for + mutable GDIMetaFileSharedPtr mpLastMtf; + + /// The canvas, mpRenderer is associated with + mutable ::basegfx::B2DHomMatrix maLastTransformation; + + const ::basegfx::B2DRectangle maBounds; + }; + + typedef ::std::shared_ptr< ViewBackgroundShape > ViewBackgroundShapeSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWBACKGROUNDSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewmediashape.cxx b/slideshow/source/engine/shapes/viewmediashape.cxx new file mode 100644 index 000000000..229599fac --- /dev/null +++ b/slideshow/source/engine/shapes/viewmediashape.cxx @@ -0,0 +1,499 @@ +/* -*- 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 <config_features.h> + +#include <tools/diagnose_ex.h> + +#include <sal/log.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/window.hxx> +#include <vcl/graph.hxx> + +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <canvas/canvastools.hxx> +#include <cppcanvas/canvas.hxx> +#include <avmedia/mediawindow.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdomedia.hxx> + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/media/XPlayer.hpp> +#include <com/sun/star/media/XPlayerWindow.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include "viewmediashape.hxx" +#include <tools.hxx> +#include <unoview.hxx> + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + ViewMediaShape::ViewMediaShape( const ViewLayerSharedPtr& rViewLayer, + const uno::Reference< drawing::XShape >& rxShape, + const uno::Reference< uno::XComponentContext >& rxContext ) : + mpViewLayer( rViewLayer ), + maWindowOffset( 0, 0 ), + maBounds(), + mxShape( rxShape ), + mxPlayer(), + mxPlayerWindow(), + mxComponentContext( rxContext ), + mbIsSoundEnabled(true) + { + ENSURE_OR_THROW( mxShape.is(), "ViewMediaShape::ViewMediaShape(): Invalid Shape" ); + ENSURE_OR_THROW( mpViewLayer, "ViewMediaShape::ViewMediaShape(): Invalid View" ); + ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewMediaShape::ViewMediaShape(): Invalid ViewLayer canvas" ); + ENSURE_OR_THROW( mxComponentContext.is(), "ViewMediaShape::ViewMediaShape(): Invalid component context" ); + + UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(rViewLayer)); + if (xUnoView) + { + mbIsSoundEnabled = xUnoView->isSoundEnabled(); + } + } + + ViewMediaShape::~ViewMediaShape() + { + try + { + endMedia(); + } + catch (const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + } + + const ViewLayerSharedPtr& ViewMediaShape::getViewLayer() const + { + return mpViewLayer; + } + + void ViewMediaShape::startMedia() + { + if( !mxPlayer.is() ) + implInitialize( maBounds ); + + if (mxPlayer.is()) + mxPlayer->start(); + } + + void ViewMediaShape::endMedia() + { + // shutdown player window + if( mxPlayerWindow.is() ) + { + mxPlayerWindow->dispose(); + mxPlayerWindow.clear(); + } + + mpMediaWindow.disposeAndClear(); + + // shutdown player + if( mxPlayer.is() ) + { + mxPlayer->stop(); + + uno::Reference< lang::XComponent > xComponent( mxPlayer, uno::UNO_QUERY ); + + if( xComponent.is() ) + xComponent->dispose(); + + mxPlayer.clear(); + } + } + + void ViewMediaShape::pauseMedia() + { + if (mxPlayer.is()) + mxPlayer->stop(); + } + + void ViewMediaShape::setMediaTime(double fTime) + { + if (mxPlayer.is()) + mxPlayer->setMediaTime(fTime); + } + + void ViewMediaShape::setLooping(bool bLooping) + { + if (mxPlayer.is()) + { + mxPlayer->setPlaybackLoop(bLooping); + } + } + + bool ViewMediaShape::render( const ::basegfx::B2DRectangle& rBounds ) const + { +#if !HAVE_FEATURE_AVMEDIA + (void) rBounds; +#else + ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas(); + + if( !pCanvas ) + return false; + + if( !mpMediaWindow && !mxPlayerWindow.is() ) + { + uno::Reference< graphic::XGraphic > xGraphic; + uno::Reference< beans::XPropertySet > xPropSet( mxShape, uno::UNO_QUERY ); + if (xPropSet.is()) + { + xPropSet->getPropertyValue("FallbackGraphic") >>= xGraphic; + } + + Graphic aGraphic(xGraphic); + const BitmapEx aBmp = aGraphic.GetBitmapEx(); + + uno::Reference< rendering::XBitmap > xBitmap(vcl::unotools::xBitmapFromBitmapEx(aBmp)); + + rendering::ViewState aViewState; + aViewState.AffineTransform = pCanvas->getViewState().AffineTransform; + + rendering::RenderState aRenderState; + ::canvas::tools::initRenderState( aRenderState ); + + const ::Size aBmpSize( aBmp.GetSizePixel() ); + + const ::basegfx::B2DVector aScale( rBounds.getWidth() / aBmpSize.Width(), + rBounds.getHeight() / aBmpSize.Height() ); + const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createScaleTranslateB2DHomMatrix( + aScale, rBounds.getMinimum())); + ::canvas::tools::setRenderStateTransform( aRenderState, aTranslation ); + + pCanvas->getUNOCanvas()->drawBitmap( xBitmap, + aViewState, + aRenderState ); + } +#endif + return true; + } + + bool ViewMediaShape::resize( const ::basegfx::B2DRectangle& rNewBounds ) const + { + maBounds = rNewBounds; + + ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas(); + + if( !pCanvas ) + return false; + + if( !mxPlayerWindow.is() ) + return true; + + uno::Reference< beans::XPropertySet > xPropSet( pCanvas->getUNOCanvas()->getDevice(), + uno::UNO_QUERY ); + + uno::Reference< awt::XWindow > xParentWindow; + if( xPropSet.is() && + getPropertyValue( xParentWindow, + xPropSet, + "Window") ) + { + const awt::Rectangle aRect( xParentWindow->getPosSize() ); + + maWindowOffset.X = aRect.X; + maWindowOffset.Y = aRect.Y; + } + + ::basegfx::B2DRange aTmpRange; + ::canvas::tools::calcTransformedRectBounds( aTmpRange, + rNewBounds, + mpViewLayer->getTransformation() ); + const ::basegfx::B2IRange& rRangePix( + ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange )); + + mxPlayerWindow->setEnable( !rRangePix.isEmpty() ); + + if( rRangePix.isEmpty() ) + return true; + + awt::Rectangle aCanvasArea; + UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(mpViewLayer)); + if (xUnoView) + aCanvasArea = xUnoView->getUnoView()->getCanvasArea(); + + const Point aPosPixel( rRangePix.getMinX() + maWindowOffset.X + aCanvasArea.X, + rRangePix.getMinY() + maWindowOffset.Y + aCanvasArea.Y ); + const Size aSizePixel( rRangePix.getMaxX() - rRangePix.getMinX(), + rRangePix.getMaxY() - rRangePix.getMinY() ); + + if( mpMediaWindow ) + { + mpMediaWindow->SetPosSizePixel( aPosPixel, aSizePixel ); + mxPlayerWindow->setPosSize( 0, 0, + aSizePixel.Width(), aSizePixel.Height(), + 0 ); + } + else + { + mxPlayerWindow->setPosSize( aPosPixel.X(), aPosPixel.Y(), + aSizePixel.Width(), aSizePixel.Height(), + 0 ); + } + + return true; + } + + + bool ViewMediaShape::implInitialize( const ::basegfx::B2DRectangle& rBounds ) + { + if( !mxPlayer.is() && mxShape.is() ) + { + ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), + "ViewMediaShape::implInitialize(): Invalid layer canvas" ); + + uno::Reference< rendering::XCanvas > xCanvas( mpViewLayer->getCanvas()->getUNOCanvas() ); + + if( xCanvas.is() ) + { + uno::Reference< beans::XPropertySet > xPropSet; + try + { + xPropSet.set( mxShape, uno::UNO_QUERY ); + OUString sMimeType; + + // create Player + if (xPropSet.is()) + { + OUString aURL; + xPropSet->getPropertyValue("MediaMimeType") >>= sMimeType; + if ((xPropSet->getPropertyValue("PrivateTempFileURL") >>= aURL) + && !aURL.isEmpty()) + { + implInitializeMediaPlayer( aURL, sMimeType ); + } + else if (xPropSet->getPropertyValue("MediaURL") >>= aURL) + { + implInitializeMediaPlayer( aURL, sMimeType ); + } + } + + // create visible object + uno::Sequence< uno::Any > aDeviceParams; + + if( ::canvas::tools::getDeviceInfo( xCanvas, aDeviceParams ).getLength() > 1 ) + { + implInitializePlayerWindow( rBounds, aDeviceParams ); + } + + // set player properties + implSetMediaProperties( xPropSet ); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + } + + return mxPlayer.is() || mxPlayerWindow.is(); + } + + + void ViewMediaShape::implSetMediaProperties( const uno::Reference< beans::XPropertySet >& rxProps ) + { + if( !mxPlayer.is() ) + return; + + mxPlayer->setMediaTime( 0.0 ); + + if( !rxProps.is() ) + return; + + bool bLoop( false ); + getPropertyValue( bLoop, + rxProps, + "Loop"); + mxPlayer->setPlaybackLoop( bLoop ); + + bool bMute( false ); + getPropertyValue( bMute, + rxProps, + "Mute"); + mxPlayer->setMute( bMute || !mbIsSoundEnabled); + + sal_Int16 nVolumeDB(0); + getPropertyValue( nVolumeDB, + rxProps, + "VolumeDB"); + mxPlayer->setVolumeDB( nVolumeDB ); + + if( mxPlayerWindow.is() ) + { + media::ZoomLevel eZoom(media::ZoomLevel_FIT_TO_WINDOW); + getPropertyValue( eZoom, + rxProps, + "Zoom"); + mxPlayerWindow->setZoomLevel( eZoom ); + } + } + + + void ViewMediaShape::implInitializeMediaPlayer( const OUString& rMediaURL, const OUString& rMimeType ) + { +#if !HAVE_FEATURE_AVMEDIA + (void) rMediaURL; + (void) rMimeType; +#else + if( mxPlayer.is() ) + return; + + try + { + if( !rMediaURL.isEmpty() ) + { + mxPlayer = avmedia::MediaWindow::createPlayer( rMediaURL, ""/*TODO!*/, &rMimeType ); + } + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( const uno::Exception& ) + { + throw lang::NoSupportException( "No video support for " + rMediaURL ); + } +#endif + } + + + void ViewMediaShape::implInitializePlayerWindow( const ::basegfx::B2DRectangle& rBounds, + const uno::Sequence< uno::Any >& rVCLDeviceParams ) + { + SAL_INFO("slideshow", "ViewMediaShape::implInitializePlayerWindow" ); + if( mpMediaWindow || rBounds.isEmpty() ) + return; + + try + { + sal_Int64 aVal=0; + + rVCLDeviceParams[ 1 ] >>= aVal; + + OutputDevice* pDevice = reinterpret_cast<OutputDevice*>(aVal); + vcl::Window* pWindow = pDevice ? pDevice->GetOwnerWindow() : nullptr; + + if( pWindow ) + { + ::basegfx::B2DRange aTmpRange; + ::canvas::tools::calcTransformedRectBounds( aTmpRange, + rBounds, + mpViewLayer->getTransformation() ); + const ::basegfx::B2IRange& rRangePix( + ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange )); + + if( !rRangePix.isEmpty() ) + { + awt::Rectangle aAWTRect( rRangePix.getMinX(), + rRangePix.getMinY(), + rRangePix.getMaxX() - rRangePix.getMinX(), + rRangePix.getMaxY() - rRangePix.getMinY() ); + { + mpMediaWindow.disposeAndClear(); + mpMediaWindow = VclPtr<SystemChildWindow>::Create( pWindow, WB_CLIPCHILDREN ); + UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(mpViewLayer)); + if (xUnoView) + { + awt::Rectangle aCanvasArea = xUnoView->getUnoView()->getCanvasArea(); + aAWTRect.X += aCanvasArea.X; + aAWTRect.Y += aCanvasArea.Y; + } + mpMediaWindow->SetPosSizePixel( Point( aAWTRect.X, aAWTRect.Y ), + Size( aAWTRect.Width, aAWTRect.Height ) ); + } + mpMediaWindow->SetBackground( COL_BLACK ); + mpMediaWindow->SetParentClipMode( ParentClipMode::NoClip ); + mpMediaWindow->EnableEraseBackground( false ); + mpMediaWindow->SetForwardKey( true ); + mpMediaWindow->SetMouseTransparent( true ); + mpMediaWindow->Show(); + + if( mxPlayer.is() ) + { + sal_IntPtr nParentWindowHandle(0); + const SystemEnvData* pEnvData = mpMediaWindow->GetSystemData(); + // tdf#139609 gtk doesn't need the handle, and fetching it is undesirable + if (!pEnvData || pEnvData->toolkit != SystemEnvData::Toolkit::Gtk) + nParentWindowHandle = mpMediaWindow->GetParentWindowHandle(); + + aAWTRect.X = aAWTRect.Y = 0; + + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + auto pMediaObj = dynamic_cast<SdrMediaObj*>(pObj); + const avmedia::MediaItem* pMediaItem = nullptr; + if (pMediaObj) + { + pMediaItem = &pMediaObj->getMediaProperties(); + } + + uno::Sequence< uno::Any > aArgs{ + uno::Any(nParentWindowHandle), + uno::Any(aAWTRect), + uno::Any(reinterpret_cast< sal_IntPtr >( mpMediaWindow.get() )), + // Media item contains media properties, e.g. cropping. + uno::Any(reinterpret_cast< sal_IntPtr >( pMediaItem )) + }; + + mxPlayerWindow.set( mxPlayer->createPlayerWindow( aArgs ) ); + + if( mxPlayerWindow.is() ) + { + mxPlayerWindow->setVisible( true ); + mxPlayerWindow->setEnable( true ); + } + } + + if( !mxPlayerWindow.is() ) + { + //if there was no playerwindow, then clear the mpMediaWindow too + //so that we can draw a placeholder instead in that space + mpMediaWindow.disposeAndClear(); + } + } + } + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewmediashape.hxx b/slideshow/source/engine/shapes/viewmediashape.hxx new file mode 100644 index 000000000..69445a8a5 --- /dev/null +++ b/slideshow/source/engine/shapes/viewmediashape.hxx @@ -0,0 +1,168 @@ +/* -*- 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_SHAPES_VIEWMEDIASHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWMEDIASHAPE_HXX + +#include <basegfx/range/b2drectangle.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/drawing/XShape.hpp> + +#include <memory> +#include <vcl/vclptr.hxx> + +#include <viewlayer.hxx> + +class SystemChildWindow; + +namespace com::sun::star { + namespace drawing { + class XShape; + } + namespace media { + class XPlayer; + class XPlayerWindow; + } + namespace uno { + class XComponentContext; + } + namespace beans{ + class XPropertySet; + } +} + +namespace slideshow::internal + { + /** This class is the viewable representation of a draw + document's media object, associated to a specific View + + The class is able to render the associated media shape on + View implementations. + */ + class ViewMediaShape final + { + public: + /** Create a ViewMediaShape for the given View + + @param rView + The associated View object. + */ + ViewMediaShape( const ViewLayerSharedPtr& rViewLayer, + const css::uno::Reference< css::drawing::XShape >& rxShape, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + /** destroy the object + */ + ~ViewMediaShape(); + + /// Forbid copy construction + ViewMediaShape(const ViewMediaShape&) = delete; + /// Forbid copy assignment + ViewMediaShape& operator=(const ViewMediaShape&) = delete; + + /** Query the associated view layer of this shape + */ + const ViewLayerSharedPtr& getViewLayer() const; + + // animation methods + + + /** Notify the ViewShape that an animation starts now + + This method enters animation mode on the associate + target view. The shape can be animated in parallel on + different views. + */ + void startMedia(); + + /** Notify the ViewShape that it is no longer animated + + This methods ends animation mode on the associate + target view + */ + void endMedia(); + + /** Notify the ViewShape that it should pause playback + + This methods pauses animation on the associate + target view. The content stays visible (for video) + */ + void pauseMedia(); + + /** Set current time of media. + + @param fTime + Local media time that should now be presented, in seconds. + */ + void setMediaTime(double fTime); + + void setLooping(bool bLooping); + + // render methods + + + /** Render the ViewShape + + This method renders the ViewMediaShape on the associated view. + + @param rBounds + The current media shape bounds + + @return whether the rendering finished successfully. + */ + bool render( const ::basegfx::B2DRectangle& rBounds ) const; + + /** Resize the ViewShape + + This method updates the ViewMediaShape size on the + associated view. It does not render. + + @param rBounds + The current media shape bounds + + @return whether the resize finished successfully. + */ + bool resize( const ::basegfx::B2DRectangle& rNewBounds ) const; + + private: + + bool implInitialize( const ::basegfx::B2DRectangle& rBounds ); + void implSetMediaProperties( const css::uno::Reference< css::beans::XPropertySet >& rxProps ); + void implInitializeMediaPlayer( const OUString& rMediaURL, const OUString& rMimeType ); + void implInitializePlayerWindow( const ::basegfx::B2DRectangle& rBounds, + const css::uno::Sequence< css::uno::Any >& rVCLDeviceParams ); + ViewLayerSharedPtr mpViewLayer; + VclPtr< SystemChildWindow > mpMediaWindow; + mutable css::awt::Point maWindowOffset; + mutable ::basegfx::B2DRectangle maBounds; + + css::uno::Reference< css::drawing::XShape > mxShape; + css::uno::Reference< css::media::XPlayer > mxPlayer; + css::uno::Reference< css::media::XPlayerWindow > mxPlayerWindow; + css::uno::Reference< css::uno::XComponentContext> mxComponentContext; + bool mbIsSoundEnabled; + }; + + typedef ::std::shared_ptr< ViewMediaShape > ViewMediaShapeSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWMEDIASHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewshape.cxx b/slideshow/source/engine/shapes/viewshape.cxx new file mode 100644 index 000000000..f2d909524 --- /dev/null +++ b/slideshow/source/engine/shapes/viewshape.cxx @@ -0,0 +1,856 @@ +/* -*- 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 <tools/diagnose_ex.h> + +#include <algorithm> + +#include <rtl/math.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/rendering/PanoseLetterForm.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <canvas/canvastools.hxx> +#include <cppcanvas/vclfactory.hxx> +#include <cppcanvas/basegfxfactory.hxx> + +#include "viewshape.hxx" +#include <tools.hxx> + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + + // TODO(F2): Provide sensible setup for mtf-related attributes (fill mode, + // char rotation etc.). Do that via mtf argument at this object + + bool ViewShape::prefetch( RendererCacheEntry& io_rCacheEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ) + { + ENSURE_OR_RETURN_FALSE( rMtf, + "ViewShape::prefetch(): no valid metafile!" ); + + if( rMtf != io_rCacheEntry.mpMtf || + rDestinationCanvas != io_rCacheEntry.getDestinationCanvas() ) + { + // buffered renderer invalid, re-create + ::cppcanvas::Renderer::Parameters aParms; + + // rendering attribute override parameter struct. For + // every valid attribute, the corresponding struct + // member is filled, which in the metafile renderer + // forces rendering with the given attribute. + if( rAttr ) + { + if( rAttr->isFillColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maFillColor = + rAttr->getFillColor().getIntegerColor(); + } + if( rAttr->isLineColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maLineColor = + rAttr->getLineColor().getIntegerColor(); + } + if( rAttr->isCharColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maTextColor = + rAttr->getCharColor().getIntegerColor(); + } + if( rAttr->isDimColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + + // dim color overrides all other colors + aParms.maFillColor = + aParms.maLineColor = + aParms.maTextColor = + rAttr->getDimColor().getIntegerColor(); + } + if( rAttr->isFontFamilyValid() ) + { + aParms.maFontName = + rAttr->getFontFamily(); + } + if( rAttr->isCharScaleValid() ) + { + ::basegfx::B2DHomMatrix aMatrix; + + // enlarge text by given scale factor. Do that + // with the middle of the shape as the center + // of scaling. + aMatrix.translate( -0.5, -0.5 ); + aMatrix.scale( rAttr->getCharScale(), + rAttr->getCharScale() ); + aMatrix.translate( 0.5, 0.5 ); + + aParms.maTextTransformation = aMatrix; + } + if( rAttr->isCharWeightValid() ) + { + aParms.maFontWeight = + static_cast< sal_Int8 >( + ::basegfx::fround( + ::std::max( 0.0, + ::std::min( 11.0, + rAttr->getCharWeight() / 20.0 ) ) ) ); + } + if( rAttr->isCharPostureValid() ) + { + aParms.maFontLetterForm = + rAttr->getCharPosture() == sal_Int16(awt::FontSlant_NONE) ? + rendering::PanoseLetterForm::ANYTHING : + rendering::PanoseLetterForm::OBLIQUE_CONTACT; + } + if( rAttr->isUnderlineModeValid() ) + { + aParms.maFontUnderline = + rAttr->getUnderlineMode(); + } + } + + io_rCacheEntry.mpRenderer = ::cppcanvas::VCLFactory::createRenderer( rDestinationCanvas, + *rMtf, + aParms ); + + io_rCacheEntry.mpMtf = rMtf; + io_rCacheEntry.mpDestinationCanvas = rDestinationCanvas; + + // also invalidate alpha compositing bitmap (created + // new renderer, which possibly generates different + // output). Do NOT invalidate, if we're incidentally + // rendering INTO it. + if( rDestinationCanvas != io_rCacheEntry.mpLastBitmapCanvas ) + { + io_rCacheEntry.mpLastBitmapCanvas.reset(); + io_rCacheEntry.mpLastBitmap.reset(); + } + } + + return static_cast< bool >(io_rCacheEntry.mpRenderer); + } + + bool ViewShape::draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr, + const ::basegfx::B2DHomMatrix& rTransform, + const ::basegfx::B2DPolyPolygon* pClip, + const VectorOfDocTreeNodes& rSubsets ) const + { + ::cppcanvas::RendererSharedPtr pRenderer( + getRenderer( rDestinationCanvas, rMtf, rAttr ) ); + + ENSURE_OR_RETURN_FALSE( pRenderer, "ViewShape::draw(): Invalid renderer" ); + + pRenderer->setTransformation( rTransform ); +#if OSL_DEBUG_LEVEL >= 2 + rendering::RenderState aRenderState; + ::canvas::tools::initRenderState(aRenderState); + ::canvas::tools::setRenderStateTransform(aRenderState, + rTransform); + aRenderState.DeviceColor.realloc(4); + aRenderState.DeviceColor[0] = 1.0; + aRenderState.DeviceColor[1] = 0.0; + aRenderState.DeviceColor[2] = 0.0; + aRenderState.DeviceColor[3] = 1.0; + + try + { + rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(0.0,0.0), + geometry::RealPoint2D(1.0,1.0), + rDestinationCanvas->getViewState(), + aRenderState ); + rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(1.0,0.0), + geometry::RealPoint2D(0.0,1.0), + rDestinationCanvas->getViewState(), + aRenderState ); + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("slideshow"); + } +#endif + if( pClip ) + pRenderer->setClip( *pClip ); + else + pRenderer->setClip(); + + if( rSubsets.empty() ) + { + return pRenderer->draw(); + } + else + { + // render subsets of whole metafile + + + bool bRet(true); + for( const auto& rSubset : rSubsets ) + { + if( !pRenderer->drawSubset( rSubset.getStartIndex(), + rSubset.getEndIndex() ) ) + bRet = false; + } + + return bRet; + } + } + + namespace + { + /// Convert untransformed shape update area to device pixel. + ::basegfx::B2DRectangle shapeArea2AreaPixel( const ::basegfx::B2DHomMatrix& rCanvasTransformation, + const ::basegfx::B2DRectangle& rUntransformedArea ) + { + // convert area to pixel, and add anti-aliasing border + + // TODO(P1): Should the view transform some + // day contain rotation/shear, transforming + // the original bounds with the total + // transformation might result in smaller + // overall bounds. + + ::basegfx::B2DRectangle aBoundsPixel; + ::canvas::tools::calcTransformedRectBounds( aBoundsPixel, + rUntransformedArea, + rCanvasTransformation ); + + // add antialiasing border around the shape (AA + // touches pixel _outside_ the nominal bound rect) + aBoundsPixel.grow( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); + + return aBoundsPixel; + } + + /// Convert shape unit rect to device pixel. + ::basegfx::B2DRectangle calcUpdateAreaPixel( const ::basegfx::B2DRectangle& rUnitBounds, + const ::basegfx::B2DHomMatrix& rShapeTransformation, + const ::basegfx::B2DHomMatrix& rCanvasTransformation, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + // calc update area for whole shape (including + // character scaling) + return shapeArea2AreaPixel( rCanvasTransformation, + getShapeUpdateArea( rUnitBounds, + rShapeTransformation, + pAttr ) ); + } + } + + bool ViewShape::renderSprite( const ViewLayerSharedPtr& rViewLayer, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rOrigBounds, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUnitBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + double nPrio, + bool bIsVisible ) const + { + // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, + // in that all the common setup steps here are refactored to Shape (would then + // have to be performed only _once_ per Shape paint). + + if( !bIsVisible || + rUnitBounds.isEmpty() || + rOrigBounds.isEmpty() || + rBounds.isEmpty() ) + { + // shape is invisible or has zero size, no need to + // update anything. + if( mpSprite ) + mpSprite->hide(); + + return true; + } + + + // calc sprite position, size and content transformation + // ===================================================== + + // the shape transformation for a sprite is always a + // simple scale-up to the nominal shape size. Everything + // else is handled via the sprite transformation + ::basegfx::B2DHomMatrix aNonTranslationalShapeTransformation; + aNonTranslationalShapeTransformation.scale( rOrigBounds.getWidth(), + rOrigBounds.getHeight() ); + ::basegfx::B2DHomMatrix aShapeTransformation( aNonTranslationalShapeTransformation ); + aShapeTransformation.translate( rOrigBounds.getMinX(), + rOrigBounds.getMinY() ); + + const ::basegfx::B2DHomMatrix& rCanvasTransform( + rViewLayer->getSpriteTransformation() ); + + // area actually needed for the sprite + const ::basegfx::B2DRectangle& rSpriteBoundsPixel( + calcUpdateAreaPixel( rUnitBounds, + aShapeTransformation, + rCanvasTransform, + pAttr ) ); + + // actual area for the shape (without subsetting, but + // including char scaling) + const ::basegfx::B2DRectangle& rShapeBoundsPixel( + calcUpdateAreaPixel( ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), + aShapeTransformation, + rCanvasTransform, + pAttr ) ); + + // nominal area for the shape (without subsetting, without + // char scaling). NOTE: to cancel the shape translation, + // contained in rSpriteBoundsPixel, this is _without_ any + // translational component. + ::basegfx::B2DRectangle aLogShapeBounds; + const ::basegfx::B2DRectangle& rNominalShapeBoundsPixel( + shapeArea2AreaPixel( rCanvasTransform, + ::canvas::tools::calcTransformedRectBounds( + aLogShapeBounds, + ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), + aNonTranslationalShapeTransformation ) ) ); + + // create (or resize) sprite with sprite's pixel size, if + // not done already + const ::basegfx::B2DSize& rSpriteSizePixel(rSpriteBoundsPixel.getRange()); + if( !mpSprite ) + { + mpSprite = std::make_shared<AnimatedSprite>( mpViewLayer, + rSpriteSizePixel, + nPrio ); + } + else + { + // TODO(F2): when the sprite _actually_ gets resized, + // content needs a repaint! + mpSprite->resize( rSpriteSizePixel ); + } + + ENSURE_OR_RETURN_FALSE( mpSprite, "ViewShape::renderSprite(): No sprite" ); + + SAL_INFO("slideshow", "ViewShape::renderSprite(): Rendering sprite " << + mpSprite.get() ); + + + // always show the sprite (might have been hidden before) + mpSprite->show(); + + // determine center of sprite output position in pixel + // (assumption here: all shape transformations have the + // shape center as the pivot point). From that, subtract + // distance of rSpriteBoundsPixel's left, top edge from + // rShapeBoundsPixel's center. This moves the sprite at + // the appropriate output position within the virtual + // rShapeBoundsPixel area. + ::basegfx::B2DPoint aSpritePosPixel( rBounds.getCenter() ); + aSpritePosPixel *= rCanvasTransform; + aSpritePosPixel -= rShapeBoundsPixel.getCenter() - rSpriteBoundsPixel.getMinimum(); + + // the difference between rShapeBoundsPixel and + // rSpriteBoundsPixel upper, left corner is: the offset we + // have to move sprite output to the right, top (to make + // the desired subset content visible at all) + const ::basegfx::B2DSize& rSpriteCorrectionOffset( + rSpriteBoundsPixel.getMinimum() - rNominalShapeBoundsPixel.getMinimum() ); + + // offset added top, left for anti-aliasing (otherwise, + // shapes fully filling the sprite will have anti-aliased + // pixel cut off) + const ::basegfx::B2DSize aAAOffset( + ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE, + ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); + + // set pixel output offset to sprite: we always leave + // ANTIALIASING_EXTRA_SIZE room atop and to the left, and, + // what's more, for subsetted shapes, we _have_ to cancel + // the effect of the shape renderer outputting the subset + // at its absolute position inside the shape, instead of + // at the origin. + // NOTE: As for now, sprites are always positioned on + // integer pixel positions on screen, have to round to + // nearest integer here, too + mpSprite->setPixelOffset( + aAAOffset - ::basegfx::B2DSize( + ::basegfx::fround( rSpriteCorrectionOffset.getX() ), + ::basegfx::fround( rSpriteCorrectionOffset.getY() ) ) ); + + // always set sprite position and transformation, since + // they do not relate directly to the update flags + // (e.g. sprite position changes when sprite size changes) + mpSprite->movePixel( aSpritePosPixel ); + mpSprite->transform( getSpriteTransformation( rSpriteSizePixel, + rOrigBounds.getRange(), + pAttr ) ); + + + // process flags + // ============= + + bool bRedrawRequired( mbForceUpdate || (nUpdateFlags & UpdateFlags::Force) ); + + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Alpha) ) + { + mpSprite->setAlpha( (pAttr && pAttr->isAlphaValid()) ? + std::clamp(pAttr->getAlpha(), + 0.0, + 1.0) : + 1.0 ); + } + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Clip) ) + { + if( pAttr && pAttr->isClipValid() ) + { + ::basegfx::B2DPolyPolygon aClipPoly( pAttr->getClip() ); + + // extract linear part of canvas view transformation + // (linear means: without translational components) + ::basegfx::B2DHomMatrix aViewTransform( + mpViewLayer->getTransformation() ); + aViewTransform.set( 0, 2, 0.0 ); + aViewTransform.set( 1, 2, 0.0 ); + + // make the clip 2*ANTIALIASING_EXTRA_SIZE larger + // such that it's again centered over the sprite. + aViewTransform.scale(rSpriteSizePixel.getX()/ + (rSpriteSizePixel.getX()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE), + rSpriteSizePixel.getY()/ + (rSpriteSizePixel.getY()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE)); + + // transform clip polygon from view to device + // coordinate space + aClipPoly.transform( aViewTransform ); + + mpSprite->clip( aClipPoly ); + } + else + mpSprite->clip(); + } + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Content) ) + { + bRedrawRequired = true; + + // TODO(P1): maybe provide some appearance change methods at + // the Renderer interface + + // force the renderer to be regenerated below, for the + // different attributes to take effect + invalidateRenderer(); + } + + mbForceUpdate = false; + + if( !bRedrawRequired ) + return true; + + + // sprite needs repaint - output to sprite canvas + // ============================================== + + ::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() ); + + return draw( pContentCanvas, + rMtf, + pAttr, + aShapeTransformation, + nullptr, // clipping is done via Sprite::clip() + rSubsets ); + } + + bool ViewShape::render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUpdateBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + bool bIsVisible ) const + { + // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, + // in that all the common setup steps here are refactored to Shape (would then + // have to be performed only _once_ per Shape paint). + + if( !bIsVisible ) + { + SAL_INFO("slideshow", "ViewShape::render(): skipping shape " << this ); + + // shape is invisible, no need to update anything. + return true; + } + + // since we have no sprite here, _any_ update request + // translates into a required redraw. + bool bRedrawRequired( mbForceUpdate || nUpdateFlags != UpdateFlags::NONE ); + + if( nUpdateFlags & UpdateFlags::Content ) + { + // TODO(P1): maybe provide some appearance change methods at + // the Renderer interface + + // force the renderer to be regenerated below, for the + // different attributes to take effect + invalidateRenderer(); + } + + mbForceUpdate = false; + + if( !bRedrawRequired ) + return true; + + SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " << + this << + " at position (" << + rBounds.getMinX() << "," << + rBounds.getMinY() << ")" ); + + + // shape needs repaint - setup all that's needed + + + std::optional<basegfx::B2DPolyPolygon> aClip; + + if( pAttr ) + { + // setup clip poly + if( pAttr->isClipValid() ) + aClip = pAttr->getClip(); + + // emulate global shape alpha by first rendering into + // a temp bitmap, and then to screen (this would have + // been much easier if we'd be currently a sprite - + // see above) + if( pAttr->isAlphaValid() ) + { + const double nAlpha( pAttr->getAlpha() ); + + if( !::basegfx::fTools::equalZero( nAlpha ) && + !::rtl::math::approxEqual(nAlpha, 1.0) ) + { + // render with global alpha - have to prepare + // a bitmap, and render that with modulated + // alpha + + + const ::basegfx::B2DHomMatrix aTransform( + getShapeTransformation( rBounds, + pAttr ) ); + + // TODO(P1): Should the view transform some + // day contain rotation/shear, transforming + // the original bounds with the total + // transformation might result in smaller + // overall bounds. + + // determine output rect of _shape update + // area_ in device pixel + const ::basegfx::B2DHomMatrix aCanvasTransform( + rDestinationCanvas->getTransformation() ); + ::basegfx::B2DRectangle aTmpRect; + ::canvas::tools::calcTransformedRectBounds( aTmpRect, + rUpdateBounds, + aCanvasTransform ); + + // pixel size of cache bitmap: round up to + // nearest int + const ::basegfx::B2ISize aBmpSize( static_cast<sal_Int32>( aTmpRect.getWidth() )+1, + static_cast<sal_Int32>( aTmpRect.getHeight() )+1 ); + + // try to fetch temporary surface for alpha + // compositing (to achieve the global alpha + // blend effect, have to first render shape as + // a whole, then blit that surface with global + // alpha to the destination) + const RendererCacheVector::iterator aCompositingSurface( + getCacheEntry( rDestinationCanvas ) ); + + if( !aCompositingSurface->mpLastBitmapCanvas || + aCompositingSurface->mpLastBitmapCanvas->getSize() != aBmpSize ) + { + // create a bitmap of appropriate size + ::cppcanvas::BitmapSharedPtr pBitmap( + ::cppcanvas::BaseGfxFactory::createAlphaBitmap( + rDestinationCanvas, + aBmpSize ) ); + + ENSURE_OR_THROW(pBitmap, + "ViewShape::render(): Could not create compositing surface"); + + aCompositingSurface->mpDestinationCanvas = rDestinationCanvas; + aCompositingSurface->mpLastBitmap = pBitmap; + aCompositingSurface->mpLastBitmapCanvas = pBitmap->getBitmapCanvas(); + } + + // buffer aCompositingSurface iterator content + // - said one might get invalidated during + // draw() below. + ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( + aCompositingSurface->mpLastBitmapCanvas ); + + ::cppcanvas::BitmapSharedPtr pBitmap( + aCompositingSurface->mpLastBitmap); + + // setup bitmap canvas transformation - + // which happens to be the destination + // canvas transformation without any + // translational components. + + // But then, the render transformation as + // calculated by getShapeTransformation() + // above outputs the shape at its real + // destination position. Thus, we have to + // offset the output back to the origin, + // for which we simply plug in the + // negative position of the left, top edge + // of the shape's bound rect in device + // pixel into aLinearTransform below. + ::basegfx::B2DHomMatrix aAdjustedCanvasTransform( aCanvasTransform ); + aAdjustedCanvasTransform.translate( -aTmpRect.getMinX(), + -aTmpRect.getMinY() ); + + pBitmapCanvas->setTransformation( aAdjustedCanvasTransform ); + + // TODO(P2): If no update flags, or only + // alpha_update is set, we can save us the + // rendering into the bitmap (uh, it's not + // _that_ easy - for a forced redraw, + // e.g. when ending an animation, we always + // get UPDATE_FORCE here). + + // render into this bitmap + if( !draw( pBitmapCanvas, + rMtf, + pAttr, + aTransform, + !aClip ? nullptr : &(*aClip), + rSubsets ) ) + { + return false; + } + + // render bitmap to screen, with given global + // alpha. Since the bitmap already contains + // pixel-equivalent output, we have to use the + // inverse view transformation, adjusted with + // the final shape output position (note: + // cannot simply change the view + // transformation here, as that would affect a + // possibly set clip!) + ::basegfx::B2DHomMatrix aBitmapTransform( aCanvasTransform ); + OSL_ENSURE( aBitmapTransform.isInvertible(), + "ViewShape::render(): View transformation is singular!" ); + + aBitmapTransform.invert(); + + const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix( + aTmpRect.getMinX(), aTmpRect.getMinY())); + + aBitmapTransform = aBitmapTransform * aTranslation; + pBitmap->setTransformation( aBitmapTransform ); + + // finally, render bitmap alpha-modulated + pBitmap->drawAlphaModulated( nAlpha ); + + return true; + } + } + } + + // retrieve shape transformation, _with_ shape translation + // to actual page position. + const ::basegfx::B2DHomMatrix aTransform( + getShapeTransformation( rBounds, + pAttr ) ); + + return draw( rDestinationCanvas, + rMtf, + pAttr, + aTransform, + !aClip ? nullptr : &(*aClip), + rSubsets ); + } + + + ViewShape::ViewShape( const ViewLayerSharedPtr& rViewLayer ) : + mpViewLayer( rViewLayer ), + maRenderers(), + mpSprite(), + mbAnimationMode( false ), + mbForceUpdate( true ) + { + ENSURE_OR_THROW( mpViewLayer, "ViewShape::ViewShape(): Invalid View" ); + } + + const ViewLayerSharedPtr& ViewShape::getViewLayer() const + { + return mpViewLayer; + } + + ViewShape::RendererCacheVector::iterator ViewShape::getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const + { + // lookup destination canvas - is there already a renderer + // created for that target? + RendererCacheVector::iterator aIter; + const RendererCacheVector::iterator aEnd( maRenderers.end() ); + + // already there? + if( (aIter=::std::find_if( maRenderers.begin(), + aEnd, + [&rDestinationCanvas]( const RendererCacheEntry& rCacheEntry ) + { return rDestinationCanvas == rCacheEntry.getDestinationCanvas(); } ) ) == aEnd ) + { + if( maRenderers.size() >= MAX_RENDER_CACHE_ENTRIES ) + { + // cache size exceeded - prune entries. For now, + // simply remove the first one, which of course + // breaks for more complex access schemes. But in + // general, this leads to most recently used + // entries to reside at the end of the vector. + maRenderers.erase( maRenderers.begin() ); + + // ATTENTION: after this, both aIter and aEnd are + // invalid! + } + + // not yet in cache - add default-constructed cache + // entry, to have something to return + maRenderers.emplace_back( ); + aIter = maRenderers.end()-1; + } + + return aIter; + } + + ::cppcanvas::RendererSharedPtr ViewShape::getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ) const + { + // lookup destination canvas - is there already a renderer + // created for that target? + const RendererCacheVector::iterator aIter( + getCacheEntry( rDestinationCanvas ) ); + + // now we have a valid entry, either way. call prefetch() + // on it, nevertheless - maybe the metafile changed, and + // the renderer still needs an update (prefetch() will + // detect that) + if( prefetch( *aIter, + rDestinationCanvas, + rMtf, + rAttr ) ) + { + return aIter->mpRenderer; + } + else + { + // prefetch failed - renderer is invalid + return ::cppcanvas::RendererSharedPtr(); + } + } + + void ViewShape::invalidateRenderer() const + { + // simply clear the cache. Subsequent getRenderer() calls + // will regenerate the Renderers. + maRenderers.clear(); + } + + ::basegfx::B2DSize ViewShape::getAntialiasingBorder() const + { + ENSURE_OR_THROW( mpViewLayer->getCanvas(), + "ViewShape::getAntialiasingBorder(): Invalid ViewLayer canvas" ); + + const ::basegfx::B2DHomMatrix& rViewTransform( + mpViewLayer->getTransformation() ); + + // TODO(F1): As a quick shortcut (did not want to invert + // whole matrix here), taking only scale components of + // view transformation matrix. This will be wrong when + // e.g. shearing is involved. + const double nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(0,0) ); + const double nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(1,1) ); + + return ::basegfx::B2DSize( nXBorder, + nYBorder ); + } + + void ViewShape::enterAnimationMode() + { + mbForceUpdate = true; + mbAnimationMode = true; + } + + void ViewShape::leaveAnimationMode() + { + mpSprite.reset(); + mbAnimationMode = false; + mbForceUpdate = true; + } + + bool ViewShape::update( const GDIMetaFileSharedPtr& rMtf, + const RenderArgs& rArgs, + UpdateFlags nUpdateFlags, + bool bIsVisible ) const + { + ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), "ViewShape::update(): Invalid layer canvas" ); + + // Shall we render to a sprite, or to a plain canvas? + if( mbAnimationMode ) + return renderSprite( mpViewLayer, + rMtf, + rArgs.maOrigBounds, + rArgs.maBounds, + rArgs.maUnitBounds, + nUpdateFlags, + rArgs.mrAttr, + rArgs.mrSubsets, + rArgs.mnShapePriority, + bIsVisible ); + else + return render( mpViewLayer->getCanvas(), + rMtf, + rArgs.maBounds, + rArgs.maUpdateBounds, + nUpdateFlags, + rArgs.mrAttr, + rArgs.mrSubsets, + bIsVisible ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/viewshape.hxx b/slideshow/source/engine/shapes/viewshape.hxx new file mode 100644 index 000000000..c7e1d564c --- /dev/null +++ b/slideshow/source/engine/shapes/viewshape.hxx @@ -0,0 +1,319 @@ +/* -*- 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_SHAPES_VIEWSHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWSHAPE_HXX + +#include <cppcanvas/renderer.hxx> +#include <cppcanvas/bitmap.hxx> + +#include <basegfx/range/b2drectangle.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <tools.hxx> +#include <shapeattributelayer.hxx> +#include <animatedsprite.hxx> +#include <viewlayer.hxx> +#include <doctreenode.hxx> + +#include <vector> +#include <memory> + +enum class UpdateFlags +{ + NONE = 0x00, + Transformation = 0x01, + Clip = 0x02, + Alpha = 0x04, + Position = 0x08, + Content = 0x10, + Force = 0x20, +}; +namespace o3tl { + template<> struct typed_flags<UpdateFlags> : is_typed_flags<UpdateFlags, 0x3f> {}; +} + + + +namespace slideshow::internal + { + /** This class is the viewable representation of a draw + document's XShape, associated to a specific View + + The class is able to render the associated XShape on + View implementations. + */ + class ViewShape + { + public: + /** Create a ViewShape for the given View + + @param rView + The associated View object. + */ + explicit ViewShape( const ViewLayerSharedPtr& rViewLayer ); + + ///Forbid copy construction + ViewShape(const ViewShape&) = delete; + /// Forbid copy assignment + ViewShape& operator=(const ViewShape&) = delete; + + /** Query the associated view layer of this shape + */ + const ViewLayerSharedPtr& getViewLayer() const; + + /** Query dimension of a safety border around the shape for AA + + If the view performs antialiasing, this method + calculates a safety border around the shape, in the + shape coordinate system, which is guaranteed to + include every pixel touched when rendering the shape. + */ + ::basegfx::B2DSize getAntialiasingBorder() const; + + + // animation methods + + + /** Notify the ViewShape that an animation starts now + + This method enters animation mode on the associate + target view. The shape can be animated in parallel on + different views. + */ + void enterAnimationMode(); + + /** Notify the ViewShape that it is no longer animated + + This methods ends animation mode on the associate + target view + */ + void leaveAnimationMode(); + + + // render methods + + + struct RenderArgs + { + /** Create render argument struct + + @param rOrigBounds + The initial shape bounds + + @param rUpdateBounds + The area covered by the shape + + @param rBounds + The current shape bounds + + @param rAttr + The current shape attribute set. Can be NULL, for + default attributes. Attention: stored as a reference, + thus, parameter object must stay valid! + + @param rSubsets + Vector of subset rendering ranges. Attention: + stored as a reference, thus, parameter object must + stay valid! + + @param nPrio + Shape priority + */ + RenderArgs( const ::basegfx::B2DRectangle& rOrigBounds, + const ::basegfx::B2DRectangle& rUpdateBounds, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUnitBounds, + const ShapeAttributeLayerSharedPtr& rAttr, + const VectorOfDocTreeNodes& rSubsets, + double nPrio ) : + maOrigBounds( rOrigBounds ), + maUpdateBounds( rUpdateBounds ), + maBounds( rBounds ), + maUnitBounds( rUnitBounds ), + mrAttr( rAttr ), + mrSubsets( rSubsets ), + mnShapePriority( nPrio ) + { + } + + const ::basegfx::B2DRectangle maOrigBounds; + const ::basegfx::B2DRectangle maUpdateBounds; + const ::basegfx::B2DRectangle maBounds; + const ::basegfx::B2DRectangle maUnitBounds; + const ShapeAttributeLayerSharedPtr& mrAttr; + const VectorOfDocTreeNodes& mrSubsets; + const double mnShapePriority; + }; + + /** Update the ViewShape + + This method updates the ViewShape on the associated + view. If the shape is currently animated, the render + target is the sprite, otherwise the view's + canvas. This method does not render anything, if the + update flags are 0. + + @param rMtf + The metafile representation of the shape + + @param rArgs + Parameter structure, containing all necessary arguments + + @param nUpdateFlags + Bitmask of things to update. Use FORCE to force a repaint. + + @param bIsVisible + When false, the shape is fully invisible (and possibly + don't need to be painted) + + @return whether the rendering finished successfully. + */ + bool update( const GDIMetaFileSharedPtr& rMtf, + const RenderArgs& rArgs, + UpdateFlags nUpdateFlags, + bool bIsVisible ) const; + + /** Retrieve renderer for given canvas and metafile. + + If necessary, the renderer is created or updated for + the metafile and attribute layer. + + @return a renderer that renders to the given + destination canvas + */ + ::cppcanvas::RendererSharedPtr getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ) const; + + + private: + struct RendererCacheEntry + { + RendererCacheEntry() : + mpDestinationCanvas(), + mpRenderer(), + mpMtf(), + mpLastBitmap(), + mpLastBitmapCanvas() + { + } + + const ::cppcanvas::CanvasSharedPtr& getDestinationCanvas() const + { + return mpDestinationCanvas; + } + + ::cppcanvas::CanvasSharedPtr mpDestinationCanvas; + ::cppcanvas::RendererSharedPtr mpRenderer; + GDIMetaFileSharedPtr mpMtf; + ::cppcanvas::BitmapSharedPtr mpLastBitmap; + ::cppcanvas::BitmapCanvasSharedPtr mpLastBitmapCanvas; + }; + + typedef ::std::vector< RendererCacheEntry > RendererCacheVector; + + + /** Prefetch Renderer for given canvas + */ + static bool prefetch( RendererCacheEntry& io_rCacheEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ); + + /** Draw with prefetched Renderer to stored canvas + + This method draws prefetched Renderer to its + associated canvas (which happens to be mpLastCanvas). + */ + bool draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr, + const ::basegfx::B2DHomMatrix& rTransform, + const ::basegfx::B2DPolyPolygon* pClip, + const VectorOfDocTreeNodes& rSubsets ) const; + + /** Render shape to an active sprite + */ + bool renderSprite( const ViewLayerSharedPtr& rViewLayer, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rOrigBounds, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUnitBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + double nPrio, + bool bIsVisible ) const; + + /** Render shape to given canvas + */ + bool render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUpdateBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + bool bIsVisible ) const; + + enum{ MAX_RENDER_CACHE_ENTRIES=2 }; + + /** Retrieve a valid iterator to renderer cache entry + + This method ensures that an internal limit of + MAX_RENDER_CACHE_ENTRIES is not exceeded. + + @param rDestinationCanvas + Destination canvas to retrieve cache entry for + + @return a valid iterator to a renderer cache entry for + the given canvas. The entry might be + default-constructed (if newly added) + */ + RendererCacheVector::iterator getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const; + + void invalidateRenderer() const; + + /** The view layer this object is part of. + + Needed for sprite creation + */ + ViewLayerSharedPtr mpViewLayer; + + /// A set of cached mtf/canvas combinations + mutable RendererCacheVector maRenderers; + + /// The sprite object + mutable AnimatedSpriteSharedPtr mpSprite; + + /// If true, render() calls go to the sprite + mutable bool mbAnimationMode; + + /// If true, shape needs full repaint (and the sprite a setup, if any) + mutable bool mbForceUpdate; + }; + + typedef ::std::shared_ptr< ViewShape > ViewShapeSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWSHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapesubset.cxx b/slideshow/source/engine/shapesubset.cxx new file mode 100644 index 000000000..5525195fe --- /dev/null +++ b/slideshow/source/engine/shapesubset.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <shapesubset.hxx> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + ShapeSubset::ShapeSubset( const AttributableShapeSharedPtr& rOriginalShape, + const DocTreeNode& rTreeNode, + const SubsettableShapeManagerSharedPtr& rShapeManager ) : + mpOriginalShape( rOriginalShape ), + mpSubsetShape(), + maTreeNode( rTreeNode ), + mpShapeManager( rShapeManager ) + { + ENSURE_OR_THROW( mpShapeManager, + "ShapeSubset::ShapeSubset(): Invalid shape manager" ); + } + + ShapeSubset::ShapeSubset( const ShapeSubsetSharedPtr& rOriginalSubset, + const DocTreeNode& rTreeNode ) : + mpOriginalShape( rOriginalSubset->mpSubsetShape ? + rOriginalSubset->mpSubsetShape : + rOriginalSubset->mpOriginalShape ), + mpSubsetShape(), + maTreeNode( rTreeNode ), + mpShapeManager( rOriginalSubset->mpShapeManager ) + { + ENSURE_OR_THROW( mpShapeManager, + "ShapeSubset::ShapeSubset(): Invalid shape manager" ); + ENSURE_OR_THROW( rOriginalSubset->maTreeNode.isEmpty() || + (rTreeNode.getStartIndex() >= rOriginalSubset->maTreeNode.getStartIndex() && + rTreeNode.getEndIndex() <= rOriginalSubset->maTreeNode.getEndIndex()), + "ShapeSubset::ShapeSubset(): Subset is bigger than parent" ); + } + + ShapeSubset::ShapeSubset( const AttributableShapeSharedPtr& rOriginalShape, + const SubsettableShapeManagerSharedPtr& rShapeManager ) : + mpOriginalShape( rOriginalShape ), + mpSubsetShape(), + maTreeNode(), + mpShapeManager( rShapeManager ) + { + ENSURE_OR_THROW( mpShapeManager, + "ShapeSubset::ShapeSubset(): Invalid shape manager" ); + } + + ShapeSubset::~ShapeSubset() + { + try + { + // if not done yet: revoke subset from original + disableSubsetShape(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + } + + AttributableShapeSharedPtr const & ShapeSubset::getSubsetShape() const + { + return mpSubsetShape ? mpSubsetShape : mpOriginalShape; + } + + void ShapeSubset::enableSubsetShape() + { + if( !mpSubsetShape && + !maTreeNode.isEmpty() ) + { + mpSubsetShape = mpShapeManager->getSubsetShape( + mpOriginalShape, + maTreeNode ); + } + } + + void ShapeSubset::disableSubsetShape() + { + if( mpSubsetShape ) + { + mpShapeManager->revokeSubset( mpOriginalShape, + mpSubsetShape ); + mpSubsetShape.reset(); + } + } + + bool ShapeSubset::isFullSet() const + { + return maTreeNode.isEmpty(); + } + + const DocTreeNode& ShapeSubset::getSubset() const + { + return maTreeNode; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/layer.cxx b/slideshow/source/engine/slide/layer.cxx new file mode 100644 index 000000000..01ce4efa4 --- /dev/null +++ b/slideshow/source/engine/slide/layer.cxx @@ -0,0 +1,272 @@ +/* -*- 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 <basegfx/range/b2drange.hxx> +#include <basegfx/range/b1drange.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <osl/diagnose.h> + +#include "layer.hxx" + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + Layer::Layer( Dummy ) : + maViewEntries(), + maBounds(), + maNewBounds(), + mbBoundsDirty(false), + mbBackgroundLayer(true), + mbClipSet(false) + { + } + + Layer::Layer() : + maViewEntries(), + maBounds(), + maNewBounds(), + mbBoundsDirty(false), + mbBackgroundLayer(false), + mbClipSet(false) + { + } + + ViewLayerSharedPtr Layer::addView( const ViewSharedPtr& rNewView ) + { + OSL_ASSERT( rNewView ); + + ViewEntryVector::iterator aIter; + const ViewEntryVector::iterator aEnd( maViewEntries.end() ); + if( (aIter=std::find_if( maViewEntries.begin(), + aEnd, + [&rNewView]( const ViewEntry& rViewEntry ) + { return rViewEntry.getView() == rNewView; } ) ) != aEnd ) + { + // already added - just return existing layer + return aIter->mpViewLayer; + + } + + // not yet added - create new view layer + ViewLayerSharedPtr pNewLayer; + if( mbBackgroundLayer ) + pNewLayer = rNewView; + else + pNewLayer = rNewView->createViewLayer(maBounds); + + // add to local list + maViewEntries.emplace_back( rNewView, + pNewLayer ); + + return maViewEntries.back().mpViewLayer; + } + + ViewLayerSharedPtr Layer::removeView( const ViewSharedPtr& rView ) + { + OSL_ASSERT( rView ); + + ViewEntryVector::iterator aIter; + const ViewEntryVector::iterator aEnd( maViewEntries.end() ); + if( (aIter=std::find_if( maViewEntries.begin(), + aEnd, + [&rView]( const ViewEntry& rViewEntry ) + { return rViewEntry.getView() == rView; } ) ) == aEnd ) + { + // View was not added/is already removed + return ViewLayerSharedPtr(); + } + + OSL_ENSURE( std::count_if( maViewEntries.begin(), + aEnd, + [&rView]( const ViewEntry& rViewEntry ) + { return rViewEntry.getView() == rView; } ) == 1, + "Layer::removeView(): view added multiple times" ); + + ViewLayerSharedPtr pRet( aIter->mpViewLayer ); + maViewEntries.erase(aIter); + + return pRet; + } + + void Layer::setShapeViews( ShapeSharedPtr const& rShape ) const + { + rShape->clearAllViewLayers(); + + for( const auto& rViewEntry : maViewEntries ) + rShape->addViewLayer( rViewEntry.getViewLayer(), false ); + } + + void Layer::setPriority( const ::basegfx::B1DRange& rPrioRange ) + { + if( !mbBackgroundLayer ) + { + for( const auto& rViewEntry : maViewEntries ) + rViewEntry.getViewLayer()->setPriority( rPrioRange ); + } + } + + void Layer::addUpdateRange( ::basegfx::B2DRange const& rUpdateRange ) + { + // TODO(Q1): move this to B2DMultiRange + if( !rUpdateRange.isEmpty() ) + maUpdateAreas.appendElement( rUpdateRange, + basegfx::B2VectorOrientation::Positive ); + } + + void Layer::updateBounds( ShapeSharedPtr const& rShape ) + { + if( !mbBackgroundLayer ) + { + if( !mbBoundsDirty ) + maNewBounds.reset(); + + maNewBounds.expand( rShape->getUpdateArea() ); + } + + mbBoundsDirty = true; + } + + bool Layer::commitBounds() + { + mbBoundsDirty = false; + + if( mbBackgroundLayer ) + return false; + + if( maNewBounds == maBounds ) + return false; + + maBounds = maNewBounds; + if( std::count_if( maViewEntries.begin(), + maViewEntries.end(), + [this]( const ViewEntry& rViewEntry ) + { return rViewEntry.getViewLayer()->resize( this->maBounds ); } + ) == 0 ) + { + return false; + } + + // layer content invalid, update areas have wrong + // coordinates/not sensible anymore. + clearUpdateRanges(); + + return true; + } + + void Layer::clearUpdateRanges() + { + maUpdateAreas.clear(); + } + + void Layer::clearContent() + { + // clear content on all view layers + for( const auto& rViewEntry : maViewEntries ) + rViewEntry.getViewLayer()->clearAll(); + + // layer content cleared, update areas are not sensible + // anymore. + clearUpdateRanges(); + } + + class LayerEndUpdate + { + public: + LayerEndUpdate( const LayerEndUpdate& ) = delete; + LayerEndUpdate& operator=( const LayerEndUpdate& ) = delete; + explicit LayerEndUpdate( LayerSharedPtr const& rLayer ) : + mpLayer( rLayer ) + {} + + ~LayerEndUpdate() { if(mpLayer) mpLayer->endUpdate(); } + + private: + LayerSharedPtr mpLayer; + }; + + Layer::EndUpdater Layer::beginUpdate() + { + if( maUpdateAreas.count() ) + { + // perform proper layer update. That means, setup proper + // clipping, and render each shape that intersects with + // the calculated update area + ::basegfx::B2DPolyPolygon aClip( maUpdateAreas.solveCrossovers() ); + aClip = ::basegfx::utils::stripNeutralPolygons(aClip); + aClip = ::basegfx::utils::stripDispensablePolygons(aClip); + + // actually, if there happen to be shapes with zero + // update area in the maUpdateAreas vector, the + // resulting clip polygon will be empty. + if( aClip.count() ) + { + for( const auto& rViewEntry : maViewEntries ) + { + const ViewLayerSharedPtr& pViewLayer = rViewEntry.getViewLayer(); + + // set clip to all view layers and + pViewLayer->setClip( aClip ); + + // clear update area on all view layers + pViewLayer->clear(); + } + + mbClipSet = true; + } + } + + return std::make_shared<LayerEndUpdate>(shared_from_this()); + } + + void Layer::endUpdate() + { + if( mbClipSet ) + { + mbClipSet = false; + + basegfx::B2DPolyPolygon aEmptyClip; + for( const auto& rViewEntry : maViewEntries ) + rViewEntry.getViewLayer()->setClip( aEmptyClip ); + } + + clearUpdateRanges(); + } + + bool Layer::isInsideUpdateArea( ShapeSharedPtr const& rShape ) const + { + return maUpdateAreas.overlaps( rShape->getUpdateArea() ); + } + + LayerSharedPtr Layer::createBackgroundLayer() + { + return LayerSharedPtr(new Layer( BackgroundLayer )); + } + + LayerSharedPtr Layer::createLayer( ) + { + return LayerSharedPtr( new Layer ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/layer.hxx b/slideshow/source/engine/slide/layer.hxx new file mode 100644 index 000000000..73f3fcce9 --- /dev/null +++ b/slideshow/source/engine/slide/layer.hxx @@ -0,0 +1,263 @@ +/* -*- 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_SLIDE_LAYER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYER_HXX + +#include <basegfx/range/b2dpolyrange.hxx> + +#include <shape.hxx> +#include <view.hxx> + +#include <vector> +#include <memory> + + +namespace slideshow::internal + { + class LayerEndUpdate; + class Layer; + typedef ::std::shared_ptr< Layer > LayerSharedPtr; + typedef ::std::weak_ptr< Layer > LayerWeakPtr; + + + /* Definition of Layer class */ + + /** This class represents one layer of output on a Slide. + + Layers group shapes for a certain depth region of a slide. + + Since slides have a notion of depth, i.e. shapes on it + have a certain order in which they lie upon each other, + this layering must be modelled. A prime example for this + necessity are animations of shapes lying behind other + shapes. Then, everything behind the animated shape will be + in a background layer, the shape itself will be in an + animation layer, and everything before it will be in a + foreground layer (these layers are most preferably + modelled as XSprite objects internally). + + @attention All methods of this class are only supposed to + be called from the LayerManager. Normally, it shouldn't be + possible to get hold of an instance of this class at all. + */ + class Layer : public std::enable_shared_from_this<Layer> + { + public: + typedef std::shared_ptr<LayerEndUpdate> EndUpdater; + + /// Forbid copy construction + Layer(const Layer&) = delete; + /// Forbid copy assignment + Layer& operator=(const Layer&) = delete; + + /** Create background layer + + This method will create a layer without a ViewLayer, + i.e. one that displays directly on the background. + */ + static LayerSharedPtr createBackgroundLayer(); + + /** Create non-background layer + + This method will create a layer in front of the + background, to contain shapes that should appear in + front of animated objects. + */ + static LayerSharedPtr createLayer(); + + + /** Predicate, whether this layer is the special + background layer + + This method is mostly useful for checking invariants. + */ + bool isBackgroundLayer() const { return mbBackgroundLayer; } + + /** Add a view to this layer. + + If the view is already added, this method does not add + it a second time, just returning the existing ViewLayer. + + @param rNewView + New view to add to this layer. + + @return the newly generated ViewLayer for this View + */ + ViewLayerSharedPtr addView( const ViewSharedPtr& rNewView ); + + /** Remove a view + + This method removes the view from this Layer and all + shapes included herein. + + @return the ViewLayer of the removed Layer, if + any. Otherwise, NULL is returned. + */ + ViewLayerSharedPtr removeView( const ViewSharedPtr& rView ); + + /** Init shape with this layer's views + + @param rShape + The shape, that will subsequently display on this + layer's views + */ + void setShapeViews( ShapeSharedPtr const& rShape ) const; + + + /** Change layer priority range. + + The layer priority affects the position of the layer + in the z direction (i.e. before/behind which other + layers this one appears). The higher the prio, the + further on top of the layer stack this one appears. + + @param rPrioRange + The priority range of differing layers must not + intersect + */ + void setPriority( const ::basegfx::B1DRange& rPrioRange ); + + /** Add an area that needs update + + @param rUpdateRange + Area on this layer that needs update + */ + void addUpdateRange( ::basegfx::B2DRange const& rUpdateRange ); + + /** Whether any update ranges have been added + + @return true, if any non-empty addUpdateRange() calls + have been made since the last render()/update() call. + */ + bool isUpdatePending() const { return maUpdateAreas.count()!=0; } + + /** Update layer bound rect from shape bounds + */ + void updateBounds( ShapeSharedPtr const& rShape ); + + /** Commit collected layer bounds to ViewLayer + + Call this method when you're done adding new shapes to + the layer. + + @return true, if layer needed a resize (which + invalidates its content - you have to repaint all + contained shapes!) + */ + bool commitBounds(); + + /** Clear all registered update ranges + + This method clears all update ranges that are + registered at this layer. + */ + void clearUpdateRanges(); + + /** Clear whole layer content + + This method clears the whole layer content. As a + byproduct, all update ranges are cleared as well. It + makes no sense to maintain them any further, since + they only serve for partial updates. + */ + void clearContent(); + + /** Init layer update. + + This method initializes a full layer update of the + update area. When the last copy of the returned + EndUpdater is destroyed, the Layer leaves update mode + again. + + @return an update end RAII object. + */ + EndUpdater beginUpdate(); + + /** Finish layer update + + Resets clipping and transformation to normal values + */ + void endUpdate(); + + /** Check whether given shape is inside current update area. + + @return true, if the given shape is at least partially + inside the current update area. + */ + bool isInsideUpdateArea( ShapeSharedPtr const& rShape ) const; + + private: + enum Dummy{ BackgroundLayer }; + + /** Create background layer + + This constructor will create a layer without a + ViewLayer, i.e. one that displays directly on the + background. + + @param eFlag + Dummy parameter, to disambiguate from normal layer + constructor + */ + explicit Layer( Dummy eFlag ); + + /** Create non-background layer + + This constructor will create a layer in front of the + background, to contain shapes that should appear in + front of animated objects. + */ + explicit Layer(); + + struct ViewEntry + { + ViewEntry( const ViewSharedPtr& rView, + const ViewLayerSharedPtr& rViewLayer ) : + mpView( rView ), + mpViewLayer( rViewLayer ) + {} + + ViewSharedPtr mpView; + ViewLayerSharedPtr mpViewLayer; + + // for generic algo access (which needs actual functions) + const ViewSharedPtr& getView() const { return mpView; } + const ViewLayerSharedPtr& getViewLayer() const { return mpViewLayer; } + }; + + typedef ::std::vector< ViewEntry > ViewEntryVector; + + ViewEntryVector maViewEntries; + basegfx::B2DPolyRange maUpdateAreas; + basegfx::B2DRange maBounds; + basegfx::B2DRange maNewBounds; + bool mbBoundsDirty; // true, if view layers need resize + bool mbBackgroundLayer; // true, if this + // layer is the + // special + // background layer + bool mbClipSet; // true, if beginUpdate set a clip + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/layermanager.cxx b/slideshow/source/engine/slide/layermanager.cxx new file mode 100644 index 000000000..74a26d1e7 --- /dev/null +++ b/slideshow/source/engine/slide/layermanager.cxx @@ -0,0 +1,833 @@ +/* -*- 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 <basegfx/range/b1drange.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <cppcanvas/canvas.hxx> + +#include <functional> +#include <algorithm> + +#include "layermanager.hxx" + +using namespace ::com::sun::star; + +namespace +{ + // add operator!= for weak_ptr + bool notEqual( slideshow::internal::LayerWeakPtr const& rLHS, + slideshow::internal::LayerWeakPtr const& rRHS ) + { + return rLHS.lock().get() != rRHS.lock().get(); + } +} + +namespace slideshow::internal +{ + template<typename LayerFunc, + typename ShapeFunc> void LayerManager::manageViews( + LayerFunc layerFunc, + ShapeFunc shapeFunc ) + { + LayerSharedPtr pCurrLayer; + ViewLayerSharedPtr pCurrViewLayer; + for( const auto& rShape : maAllShapes ) + { + LayerSharedPtr pLayer = rShape.second.lock(); + if( pLayer && pLayer != pCurrLayer ) + { + pCurrLayer = pLayer; + pCurrViewLayer = layerFunc(pCurrLayer); + } + + if( pCurrViewLayer ) + shapeFunc(rShape.first,pCurrViewLayer); + } + } + + LayerManager::LayerManager( const UnoViewContainer& rViews, + bool bDisableAnimationZOrder ) : + mrViews(rViews), + maLayers(), + maXShapeHash( 101 ), + maAllShapes(), + maUpdateShapes(), + mnActiveSprites(0), + mbLayerAssociationDirty(false), + mbActive(false), + mbDisableAnimationZOrder(bDisableAnimationZOrder) + { + // prevent frequent resizes (won't have more than 4 layers + // for 99.9% of the cases) + maLayers.reserve(4); + + // create initial background layer + maLayers.push_back( Layer::createBackgroundLayer() ); + + // init views + for( const auto& rView : mrViews ) + viewAdded( rView ); + } + + void LayerManager::activate() + { + mbActive = true; + maUpdateShapes.clear(); // update gets forced via area, or + // has happened outside already + + // clear all possibly pending update areas - content + // is there, already + for( const auto& pLayer : maLayers ) + pLayer->clearUpdateRanges(); + + updateShapeLayers( true/*bSlideBackgroundPainted*/ ); + } + + void LayerManager::deactivate() + { + // TODO(F3): This is mostly a hack. Problem is, there's + // currently no smart way of telling shapes "remove your + // sprites". Others, like MediaShapes, listen to + // start/stop animation events, which is too much overhead + // for all shapes, though. + + const bool bMoreThanOneLayer(maLayers.size() > 1); + if (mnActiveSprites || bMoreThanOneLayer) + { + // clear all viewlayers, dump everything but the + // background layer - this will also remove all shape + // sprites + for (auto& rShape : maAllShapes) + rShape.first->clearAllViewLayers(); + + for (auto& rShape : maAllShapes) + rShape.second.reset(); + + if (bMoreThanOneLayer) + maLayers.erase(maLayers.begin() + 1, maLayers.end()); + + mbLayerAssociationDirty = true; + } + + mbActive = false; + + // only background layer left + OSL_ASSERT( maLayers.size() == 1 && maLayers.front()->isBackgroundLayer() ); + } + + void LayerManager::viewAdded( const UnoViewSharedPtr& rView ) + { + // view must be member of mrViews container + OSL_ASSERT( std::find(mrViews.begin(), + mrViews.end(), + rView) != mrViews.end() ); + + // init view content + if( mbActive ) + rView->clearAll(); + + // add View to all registered shapes + manageViews( + [&rView]( const LayerSharedPtr& pLayer ) + { return pLayer->addView( rView ); }, + []( const ShapeSharedPtr& pShape, const ViewLayerSharedPtr& pLayer ) + { return pShape->addViewLayer( pLayer, true ); } ); + + // in case we haven't reached all layers from the + // maAllShapes, issue addView again for good measure + for( const auto& pLayer : maLayers ) + pLayer->addView( rView ); + } + + void LayerManager::viewRemoved( const UnoViewSharedPtr& rView ) + { + // view must not be member of mrViews container anymore + OSL_ASSERT( std::find(mrViews.begin(), + mrViews.end(), + rView) == mrViews.end() ); + + // remove View from all registered shapes + manageViews( + [&rView]( const LayerSharedPtr& pLayer ) + { return pLayer->removeView( rView ); }, + []( const ShapeSharedPtr& pShape, const ViewLayerSharedPtr& pLayer ) + { return pShape->removeViewLayer( pLayer ); } ); + + // in case we haven't reached all layers from the + // maAllShapes, issue removeView again for good measure + for( const auto& pLayer : maLayers ) + pLayer->removeView( rView ); + } + + void LayerManager::viewChanged( const UnoViewSharedPtr& rView ) + { + // view must be member of mrViews container + OSL_ASSERT( std::find(mrViews.begin(), + mrViews.end(), + rView) != mrViews.end() ); + + // TODO(P2): selectively update only changed view + viewsChanged(); + } + + void LayerManager::viewsChanged() + { + if( !mbActive ) + return; + + // clear view area + for( const auto& pView : mrViews ) + pView->clearAll(); + + // TODO(F3): resize and repaint all layers + + // render all shapes + for( const auto& rShape : maAllShapes ) + rShape.first->render(); + } + + void LayerManager::addShape( const ShapeSharedPtr& rShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + ENSURE_OR_THROW( rShape, "LayerManager::addShape(): invalid Shape" ); + + // add shape to XShape hash map + if( !maXShapeHash.emplace(rShape->getXShape(), + rShape).second ) + { + // entry already present, nothing to do + return; + } + + // add shape to appropriate layer + implAddShape( rShape ); + } + + void LayerManager::putShape2BackgroundLayer( LayerShapeMap::value_type& rShapeEntry ) + { + LayerSharedPtr& rBgLayer( maLayers.front() ); + rBgLayer->setShapeViews(rShapeEntry.first); + rShapeEntry.second = rBgLayer; + } + + void LayerManager::implAddShape( const ShapeSharedPtr& rShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + ENSURE_OR_THROW( rShape, "LayerManager::implAddShape(): invalid Shape" ); + + LayerShapeMap::value_type aValue (rShape, LayerWeakPtr()); + + OSL_ASSERT( maAllShapes.find(rShape) == maAllShapes.end() ); // shape must not be added already + mbLayerAssociationDirty = true; + + if( mbDisableAnimationZOrder ) + putShape2BackgroundLayer( + *maAllShapes.insert(aValue).first ); + else + maAllShapes.insert(aValue); + + // update shape, it's just added and not yet painted + if( rShape->isVisible() ) + notifyShapeUpdate( rShape ); + } + + bool LayerManager::removeShape( const ShapeSharedPtr& rShape ) + { + // remove shape from XShape hash map + if( maXShapeHash.erase( rShape->getXShape() ) == 0 ) + return false; // shape not in map + + OSL_ASSERT( maAllShapes.find(rShape) != maAllShapes.end() ); + + implRemoveShape( rShape ); + + return true; + } + + void LayerManager::implRemoveShape( const ShapeSharedPtr& rShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + ENSURE_OR_THROW( rShape, "LayerManager::implRemoveShape(): invalid Shape" ); + + const LayerShapeMap::iterator aShapeEntry( maAllShapes.find(rShape) ); + + if( aShapeEntry == maAllShapes.end() ) + return; + + const bool bShapeUpdateNotified = maUpdateShapes.erase( rShape ) != 0; + + // Enter shape area to the update area, but only if shape + // is visible and not in sprite mode (otherwise, updating + // the area doesn't do actual harm, but costs time) + // Actually, also add it if it was listed in + // maUpdateShapes (might have just gone invisible). + if( bShapeUpdateNotified || + (rShape->isVisible() && + !rShape->isBackgroundDetached()) ) + { + LayerSharedPtr pLayer = aShapeEntry->second.lock(); + if( pLayer ) + { + // store area early, once the shape is removed from + // the layers, it no longer has any view references + pLayer->addUpdateRange( rShape->getUpdateArea() ); + } + } + + rShape->clearAllViewLayers(); + maAllShapes.erase( aShapeEntry ); + + mbLayerAssociationDirty = true; + } + + ShapeSharedPtr LayerManager::lookupShape( const uno::Reference< drawing::XShape >& xShape ) const + { + ENSURE_OR_THROW( xShape.is(), "LayerManager::lookupShape(): invalid Shape" ); + + const XShapeToShapeMap::const_iterator aIter( maXShapeHash.find( xShape )); + if( aIter == maXShapeHash.end() ) + return ShapeSharedPtr(); // not found + + // found, return data part of entry pair. + return aIter->second; + } + + AttributableShapeSharedPtr LayerManager::getSubsetShape( const AttributableShapeSharedPtr& rOrigShape, + const DocTreeNode& rTreeNode ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + + AttributableShapeSharedPtr pSubset; + + // shape already added? + if( rOrigShape->createSubset( pSubset, + rTreeNode ) ) + { + OSL_ENSURE( pSubset, "LayerManager::getSubsetShape(): failed to create subset" ); + + // don't add to shape hash, we're dupes to the + // original XShape anyway - all subset shapes return + // the same XShape as the original one. + + // add shape to corresponding layer + implAddShape( pSubset ); + + // update original shape, it now shows less content + // (the subset is removed from its displayed + // output). Subset shape is updated within + // implAddShape(). + if( rOrigShape->isVisible() ) + notifyShapeUpdate( rOrigShape ); + } + + return pSubset; + } + + const XShapeToShapeMap& LayerManager::getXShapeToShapeMap() const + { + return maXShapeHash; + } + + void LayerManager::revokeSubset( const AttributableShapeSharedPtr& rOrigShape, + const AttributableShapeSharedPtr& rSubsetShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + + if( rOrigShape->revokeSubset( rSubsetShape ) ) + { + OSL_ASSERT( maAllShapes.find(rSubsetShape) != maAllShapes.end() ); + + implRemoveShape( rSubsetShape ); + + // update original shape, it now shows more content + // (the subset is added back to its displayed output) + if( rOrigShape->isVisible() ) + notifyShapeUpdate( rOrigShape ); + } + } + + void LayerManager::enterAnimationMode( const AnimatableShapeSharedPtr& rShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + ENSURE_OR_THROW( rShape, "LayerManager::enterAnimationMode(): invalid Shape" ); + + const bool bPrevAnimState( rShape->isBackgroundDetached() ); + + rShape->enterAnimationMode(); + + // if this call _really_ enabled the animation mode at + // rShape, insert it to our enter animation queue, to + // perform the necessary layer reorg lazily on + // LayerManager::update()/render(). + if( bPrevAnimState != rShape->isBackgroundDetached() ) + { + ++mnActiveSprites; + mbLayerAssociationDirty = true; + + // area needs update (shape is removed from normal + // slide, and now rendered as an autonomous + // sprite). store in update set + if( rShape->isVisible() ) + addUpdateArea( rShape ); + } + + // TODO(P1): this can lead to potential wasted effort, if + // a shape gets toggled animated/unanimated a few times + // between two frames, returning to the original state. + } + + void LayerManager::leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ) + { + ENSURE_OR_THROW( !maLayers.empty(), "LayerManager::leaveAnimationMode(): no layers" ); + ENSURE_OR_THROW( rShape, "LayerManager::leaveAnimationMode(): invalid Shape" ); + + const bool bPrevAnimState( rShape->isBackgroundDetached() ); + + rShape->leaveAnimationMode(); + + // if this call _really_ ended the animation mode at + // rShape, insert it to our leave animation queue, to + // perform the necessary layer reorg lazily on + // LayerManager::update()/render(). + if( bPrevAnimState != rShape->isBackgroundDetached() ) + { + --mnActiveSprites; + mbLayerAssociationDirty = true; + + // shape needs update, no previous rendering, fast + // update possible. + if( rShape->isVisible() ) + notifyShapeUpdate( rShape ); + } + + // TODO(P1): this can lead to potential wasted effort, if + // a shape gets toggled animated/unanimated a few times + // between two frames, returning to the original state. + } + + void LayerManager::notifyShapeUpdate( const ShapeSharedPtr& rShape ) + { + if( !mbActive || mrViews.empty() ) + return; + + // hidden sprite-shape needs render() call still, to hide sprite + if( rShape->isVisible() || rShape->isBackgroundDetached() ) + maUpdateShapes.insert( rShape ); + else + addUpdateArea( rShape ); + } + + bool LayerManager::isUpdatePending() const + { + if( !mbActive ) + return false; + + if( mbLayerAssociationDirty || !maUpdateShapes.empty() ) + return true; + + return std::any_of( maLayers.begin(), + maLayers.end(), + std::mem_fn(&Layer::isUpdatePending) ); + } + + bool LayerManager::updateSprites() + { + bool bRet(true); + + // send update() calls to every shape in the + // maUpdateShapes set, which is _animated_ (i.e. a + // sprite). + for( const auto& pShape : maUpdateShapes ) + { + if( pShape->isBackgroundDetached() ) + { + // can update shape directly, without + // affecting layer content (shape is + // currently displayed in a sprite) + if( !pShape->update() ) + bRet = false; // delay error exit + } + else + { + // TODO(P2): addUpdateArea() involves log(n) + // search for shape layer. Have a frequent + // shape/layer association cache, or ptr back to + // layer at the shape? + + // cannot update shape directly, it's not + // animated and update() calls will prolly + // overwrite other page content. + addUpdateArea( pShape ); + } + } + + maUpdateShapes.clear(); + + return bRet; + } + + bool LayerManager::update() + { + bool bRet = true; + + if( !mbActive ) + return bRet; + + // going to render - better flush any pending layer reorg + // now + updateShapeLayers(false); + + // all sprites + bRet = updateSprites(); + + // any non-sprite update areas left? + if( std::none_of( maLayers.begin(), + maLayers.end(), + std::mem_fn( &Layer::isUpdatePending ) ) ) + return bRet; // nope, done. + + // update each shape on each layer, that has + // isUpdatePending() + bool bIsCurrLayerUpdating(false); + Layer::EndUpdater aEndUpdater; + LayerSharedPtr pCurrLayer; + for( const auto& rShape : maAllShapes ) + { + LayerSharedPtr pLayer = rShape.second.lock(); + if( pLayer != pCurrLayer ) + { + pCurrLayer = pLayer; + bIsCurrLayerUpdating = pCurrLayer->isUpdatePending(); + + if( bIsCurrLayerUpdating ) + aEndUpdater = pCurrLayer->beginUpdate(); + } + + if( bIsCurrLayerUpdating && + !rShape.first->isBackgroundDetached() && + pCurrLayer->isInsideUpdateArea(rShape.first) ) + { + if( !rShape.first->render() ) + bRet = false; + } + } + + return bRet; + } + + namespace + { + /** Little wrapper around a Canvas, to render one-shot + into a canvas + */ + class DummyLayer : public ViewLayer + { + public: + explicit DummyLayer( const ::cppcanvas::CanvasSharedPtr& rCanvas ) : + mpCanvas( rCanvas ) + { + } + + virtual bool isOnView(ViewSharedPtr const& /*rView*/) const override + { + return true; // visible on all views + } + + virtual ::cppcanvas::CanvasSharedPtr getCanvas() const override + { + return mpCanvas; + } + + virtual void clear() const override + { + // NOOP + } + + virtual void clearAll() const override + { + // NOOP + } + + virtual ::cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& /*rSpriteSizePixel*/, + double /*nSpritePrio*/ ) const override + { + ENSURE_OR_THROW( false, + "DummyLayer::createSprite(): This method is not supposed to be called!" ); + return ::cppcanvas::CustomSpriteSharedPtr(); + } + + virtual void setPriority( const basegfx::B1DRange& /*rRange*/ ) override + { + OSL_FAIL( "BitmapView::setPriority(): This method is not supposed to be called!" ); + } + + virtual css::geometry::IntegerSize2D getTranslationOffset() const override + { + return geometry::IntegerSize2D(0,0); + } + + virtual ::basegfx::B2DHomMatrix getTransformation() const override + { + return mpCanvas->getTransformation(); + } + + virtual ::basegfx::B2DHomMatrix getSpriteTransformation() const override + { + OSL_FAIL( "BitmapView::getSpriteTransformation(): This method is not supposed to be called!" ); + return ::basegfx::B2DHomMatrix(); + } + + virtual void setClip( const ::basegfx::B2DPolyPolygon& /*rClip*/ ) override + { + OSL_FAIL( "BitmapView::setClip(): This method is not supposed to be called!" ); + } + + virtual bool resize( const ::basegfx::B2DRange& /*rArea*/ ) override + { + OSL_FAIL( "BitmapView::resize(): This method is not supposed to be called!" ); + return false; + } + + private: + ::cppcanvas::CanvasSharedPtr mpCanvas; + }; + } + + bool LayerManager::renderTo( const ::cppcanvas::CanvasSharedPtr& rTargetCanvas ) const + { + bool bRet( true ); + ViewLayerSharedPtr pTmpLayer = std::make_shared<DummyLayer>( rTargetCanvas ); + + for( const auto& rShape : maAllShapes ) + { + try + { + // forward to all shape's addViewLayer method (which + // we request to render the Shape on the new + // ViewLayer. Since we add the shapes in the + // maShapeSet order (which is also the render order), + // this is equivalent to a subsequent render() call) + rShape.first->addViewLayer( pTmpLayer, + true ); + + // and remove again, this is only temporary + rShape.first->removeViewLayer( pTmpLayer ); + } + catch( uno::Exception& ) + { + // TODO(E1): Might be superfluous. Nowadays, + // addViewLayer swallows all errors, anyway. + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + // at least one shape could not be rendered + bRet = false; + } + } + + return bRet; + } + + void LayerManager::addUpdateArea( ShapeSharedPtr const& rShape ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + ENSURE_OR_THROW( rShape, "LayerManager::addUpdateArea(): invalid Shape" ); + + const LayerShapeMap::const_iterator aShapeEntry( maAllShapes.find(rShape) ); + + if( aShapeEntry == maAllShapes.end() ) + return; + + LayerSharedPtr pLayer = aShapeEntry->second.lock(); + if( pLayer ) + pLayer->addUpdateRange( rShape->getUpdateArea() ); + } + + void LayerManager::commitLayerChanges( std::size_t nCurrLayerIndex, + LayerShapeMap::const_iterator aFirstLayerShape, + const LayerShapeMap::const_iterator& aEndLayerShapes ) + { + const bool bLayerExists( maLayers.size() > nCurrLayerIndex ); + if( !bLayerExists ) + return; + + const LayerSharedPtr& rLayer( maLayers.at(nCurrLayerIndex) ); + const bool bLayerResized( rLayer->commitBounds() ); + rLayer->setPriority( basegfx::B1DRange(nCurrLayerIndex, + nCurrLayerIndex+1) ); + + if( !bLayerResized ) + return; + + // need to re-render whole layer - start from + // clean state + rLayer->clearContent(); + + // render and remove from update set + while( aFirstLayerShape != aEndLayerShapes ) + { + maUpdateShapes.erase(aFirstLayerShape->first); + aFirstLayerShape->first->render(); + ++aFirstLayerShape; + } + } + + LayerSharedPtr LayerManager::createForegroundLayer() const + { + OSL_ASSERT( mbActive ); + + LayerSharedPtr pLayer( Layer::createLayer() ); + + // create ViewLayers for all registered views, and add to + // newly created layer. + for( const auto& rView : mrViews ) + pLayer->addView( rView ); + + return pLayer; + } + + void LayerManager::updateShapeLayers( bool bBackgroundLayerPainted ) + { + OSL_ASSERT( !maLayers.empty() ); // always at least background layer + OSL_ASSERT( mbActive ); + + // do we need to process shapes? + if( !mbLayerAssociationDirty ) + return; + + if( mbDisableAnimationZOrder ) + { + // layer setup happened elsewhere, is only bg layer + // anyway. + mbLayerAssociationDirty = false; + return; + } + + // scan through maAllShapes, and determine shape animation + // discontinuities: when a shape that has + // isBackgroundDetached() return false follows a shape + // with isBackgroundDetached() true, the former and all + // following ones must be moved into an own layer. + + // to avoid tons of temporaries, create weak_ptr to Layers + // beforehand + std::vector< LayerWeakPtr > aWeakLayers(maLayers.begin(),maLayers.end()); + + std::size_t nCurrLayerIndex(0); + bool bIsBackgroundLayer(true); + bool bLastWasBackgroundDetached(false); // last shape sprite state + LayerShapeMap::iterator aCurrShapeEntry( maAllShapes.begin() ); + LayerShapeMap::iterator aCurrLayerFirstShapeEntry( maAllShapes.begin() ); + const LayerShapeMap::iterator aEndShapeEntry ( maAllShapes.end() ); + while( aCurrShapeEntry != aEndShapeEntry ) + { + const ShapeSharedPtr pCurrShape( aCurrShapeEntry->first ); + const bool bThisIsBackgroundDetached( + pCurrShape->isBackgroundDetached() ); + + if( bLastWasBackgroundDetached && + !bThisIsBackgroundDetached ) + { + // discontinuity found - current shape needs to + // get into a new layer + + + // commit changes to previous layer + commitLayerChanges(nCurrLayerIndex, + aCurrLayerFirstShapeEntry, + aCurrShapeEntry); + aCurrLayerFirstShapeEntry=aCurrShapeEntry; + ++nCurrLayerIndex; + bIsBackgroundLayer = false; + + if( aWeakLayers.size() <= nCurrLayerIndex || + notEqual(aWeakLayers.at(nCurrLayerIndex), aCurrShapeEntry->second) ) + { + // no more layers left, or shape was not + // member of this layer - create a new one + maLayers.insert( maLayers.begin()+nCurrLayerIndex, + createForegroundLayer() ); + aWeakLayers.insert( aWeakLayers.begin()+nCurrLayerIndex, + maLayers[nCurrLayerIndex] ); + } + } + + OSL_ASSERT( maLayers.size() == aWeakLayers.size() ); + + // note: using indices here, since vector::insert + // above invalidates iterators + LayerSharedPtr& rCurrLayer( maLayers.at(nCurrLayerIndex) ); + LayerWeakPtr& rCurrWeakLayer( aWeakLayers.at(nCurrLayerIndex) ); + if( notEqual(rCurrWeakLayer, aCurrShapeEntry->second) ) + { + // mismatch: shape is not contained in current + // layer - move shape to that layer, then. + maLayers.at(nCurrLayerIndex)->setShapeViews( + pCurrShape ); + + // layer got new shape(s), need full repaint, if + // non-sprite shape + if( !bThisIsBackgroundDetached && pCurrShape->isVisible() ) + { + LayerSharedPtr pOldLayer( aCurrShapeEntry->second.lock() ); + if( pOldLayer ) + { + // old layer still valid? then we need to + // repaint former shape area + pOldLayer->addUpdateRange( + pCurrShape->getUpdateArea() ); + } + + // render on new layer (only if not + // explicitly disabled) + if( !(bBackgroundLayerPainted && bIsBackgroundLayer) ) + maUpdateShapes.insert( pCurrShape ); + } + + aCurrShapeEntry->second = rCurrWeakLayer; + } + + // update layerbounds regardless of the fact that the + // shape might be contained in said layer + // already. updateBounds() is dumb and needs to + // collect all shape bounds. + // of course, no need to expand layer bounds for + // shapes that reside in sprites themselves. + if( !bThisIsBackgroundDetached && !bIsBackgroundLayer ) + rCurrLayer->updateBounds( pCurrShape ); + + bLastWasBackgroundDetached = bThisIsBackgroundDetached; + ++aCurrShapeEntry; + } + + // commit very last layer data + commitLayerChanges(nCurrLayerIndex, + aCurrLayerFirstShapeEntry, + aCurrShapeEntry); + + // any layers left? Bin them! + if( maLayers.size() > nCurrLayerIndex+1 ) + maLayers.erase(maLayers.begin()+nCurrLayerIndex+1, + maLayers.end()); + + mbLayerAssociationDirty = false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/layermanager.hxx b/slideshow/source/engine/slide/layermanager.hxx new file mode 100644 index 000000000..1969f0ccc --- /dev/null +++ b/slideshow/source/engine/slide/layermanager.hxx @@ -0,0 +1,363 @@ +/* -*- 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_SLIDE_LAYERMANAGER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYERMANAGER_HXX + +#include <unoviewcontainer.hxx> +#include <attributableshape.hxx> +#include "layer.hxx" +#include <tools.hxx> + +#include <memory> +#include <map> +#include <unordered_map> +#include <vector> + +namespace basegfx { + class B2DRange; +} + +namespace slideshow::internal + { + /** A hash map which maps the XShape to the corresponding Shape object. + + Provides quicker lookup than ShapeSet for simple mappings + */ + typedef std::unordered_map< + css::uno::Reference< css::drawing::XShape >, + ShapeSharedPtr, + hash< css::uno::Reference< css::drawing::XShape > > + > XShapeToShapeMap; + + /* Definition of Layermanager class */ + + /** This class manages all of a slide's layers (and shapes) + + Since layer content changes when animations start or end, + the layer manager keeps track of this and also handles + starting/stopping of Shape animations. Note that none of + the methods actually perform a screen update, this is + always delayed until the ActivitiesQueue explicitly + performs it. + + @see Layer + @see Shape + */ + class LayerManager + { + public: + /** Create a new layer manager for the given page bounds + + @param rViews + Views currently registered + + @param bDisableAnimationZOrder + When true, all sprite animations run in the + foreground. That is, no extra layers are created, and + the slideshow runs potentially faster. + */ + LayerManager( const UnoViewContainer& rViews, + bool bDisableAnimationZOrder ); + + /// Forbid copy construction + LayerManager(const LayerManager&) = delete; + + /// Forbid copy assignment + LayerManager& operator=(const LayerManager&) = delete; + + /** Activate the LayerManager + + This method activates the LayerManager. Prior to + activation, this instance will be passive, i.e. won't + render anything to any view. + */ + void activate(); + + /** Deactivate the LayerManager + + This method deactivates the LayerManager. After + deactivation, this instance will be passive, + i.e. don't render anything to any view. Furthermore, + if there's currently more than one Layer active, this + method also removes all but one. + */ + void deactivate(); + + // From ViewEventHandler, forwarded by SlideImpl + /// Notify new view added to UnoViewContainer + void viewAdded( const UnoViewSharedPtr& rView ); + /// Notify view removed from UnoViewContainer + void viewRemoved( const UnoViewSharedPtr& rView ); + void viewChanged( const UnoViewSharedPtr& rView ); + void viewsChanged(); + + /** Add the shape to this object + + This method adds a shape to the page. + */ + void addShape( const ShapeSharedPtr& rShape ); + + /** Remove shape from this object + + This method removes a shape from the shape. + */ + bool removeShape( const ShapeSharedPtr& rShape ); + + /** Lookup a Shape from an XShape model object + + This method looks up the internal shape map for one + representing the given XShape. + + @param xShape + The XShape object, for which the representing Shape + should be looked up. + */ + ShapeSharedPtr lookupShape( const css::uno::Reference< css::drawing::XShape >& xShape ) const; + + /** Query a subset of the given original shape + + This method queries a new (but not necessarily unique) + shape, which displays only the given subset of the + original one. + */ + AttributableShapeSharedPtr getSubsetShape( const AttributableShapeSharedPtr& rOrigShape, + const DocTreeNode& rTreeNode ); + + /** Get a map that maps all Shapes with their XShape reference as the key + * + * @return an unordered map that contains all shapes in the + * current page with their XShape reference as the key + */ + const XShapeToShapeMap& getXShapeToShapeMap() const; + + /** Revoke a previously queried subset shape. + + With this method, a previously requested subset shape + is revoked again. If the last client revokes a given + subset, it will cease to be displayed, and the + original shape will again show the subset data. + + @param rOrigShape + The shape the subset was created from + + @param rSubsetShape + The subset created from rOrigShape + */ + void revokeSubset( const AttributableShapeSharedPtr& rOrigShape, + const AttributableShapeSharedPtr& rSubsetShape ); + + /** Notify the LayerManager that the given Shape starts an + animation now. + + This method enters animation mode for the Shape on all + registered views. + */ + void enterAnimationMode( const AnimatableShapeSharedPtr& rShape ); + + /** Notify the LayerManager that the given Shape is no + longer animated. + + This methods ends animation mode for the given Shape + on all registered views. + */ + void leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ); + + /** Notify that a shape needs an update + + This method notifies the layer manager that a shape + update is necessary. This is useful if, during + animation playback, changes occur to shapes which make + an update necessary on an update() call. Otherwise, + update() will not render anything, which is not + triggered by calling one of the other LayerManager + methods. + + @param rShape + Shape which needs an update + */ + void notifyShapeUpdate( const ShapeSharedPtr& rShape); + + /** Check whether any update operations are pending. + + @return true, if this LayerManager has any updates + pending, i.e. needs to repaint something for the next + frame. + */ + bool isUpdatePending() const; + + /** Update the content + + This method updates the content on all layers on all + registered views. It does not issues a + View::updateScreen() call on registered views. Please + note that this method only takes into account changes + to shapes induced directly by calling methods of the + LayerManager. If a shape needs an update, because of + some external event unknown to the LayerManager (most + notably running animations), you have to notify the + LayerManager via notifyShapeUpdate(). + + @see LayerManager::updateScreen() + + @return whether the update finished successfully. + */ + bool update(); + + /** Render the content to given canvas + + This is a one-shot operation, which simply draws all + shapes onto the given canvas, without any caching or + other fuzz. Don't use that for repeated output onto + the same canvas, the View concept is more optimal + then. + + @param rTargetCanvas + Target canvas to output onto. + */ + bool renderTo( const ::cppcanvas::CanvasSharedPtr& rTargetCanvas ) const; + + private: + + class ShapeComparator + { + public: + bool operator() (const ShapeSharedPtr& rpS1, const ShapeSharedPtr& rpS2 ) const + { + return Shape::lessThanShape::compare(rpS1.get(), rpS2.get()); + } + }; + /** Set of all shapes + */ + private: + typedef ::std::map< ShapeSharedPtr, LayerWeakPtr, ShapeComparator > LayerShapeMap; + + + /// Adds shape area to containing layer's damage area + void addUpdateArea( ShapeSharedPtr const& rShape ); + + LayerSharedPtr createForegroundLayer() const; + + /** Push changes from updateShapeLayerAssociations() to current layer + + Factored-out method that resizes layer, if necessary, + assigns correct layer priority, and repaints contained shapes. + + @param nCurrLayerIndex + Index of current layer in maLayers + + @param aFirstLayerShape + Valid iterator out of maAllShapes, denoting the first + shape from nCurrLayerIndex + + @param aEndLayerShapes + Valid iterator or end iterator out of maAllShapes, + denoting one-behind-the-last shape of nCurrLayerIndex + */ + void commitLayerChanges( std::size_t nCurrLayerIndex, + LayerShapeMap::const_iterator aFirstLayerShape, + const LayerShapeMap::const_iterator& aEndLayerShapes ); + + /** Init Shape layers with background layer. + */ + void putShape2BackgroundLayer( LayerShapeMap::value_type& rShapeEntry ); + + /** Commits any pending layer reorg, due to shapes either + entering or leaving animation mode + + @param bBackgroundLayerPainted + When true, LayerManager does not render anything on + the background layer. Use this, if background has been + updated by other means (e.g. slide transition) + */ + void updateShapeLayers( bool bBackgroundLayerPainted ); + + /** Common stuff when adding a shape + */ + void implAddShape( const ShapeSharedPtr& rShape ); + + /** Common stuff when removing a shape + */ + void implRemoveShape( const ShapeSharedPtr& rShape ); + + /** Add or remove views + + Sharing duplicate code from viewAdded and viewRemoved + method. The only point of variation at those places + are removal vs. adding. + */ + template<typename LayerFunc, + typename ShapeFunc> void manageViews( LayerFunc layerFunc, + ShapeFunc shapeFunc ); + + bool updateSprites(); + + /// Registered views + const UnoViewContainer& mrViews; + + /// All layers of this object. Vector owns the layers + ::std::vector< LayerSharedPtr > + maLayers; + + /** Contains all shapes with their XShape reference as the key + */ + XShapeToShapeMap maXShapeHash; + + /** Set of shapes this LayerManager own + + Contains the same set of shapes as XShapeHash, but is + sorted in z order, for painting and layer + association. Set entries are enriched with two flags + for buffering animation enable/disable changes, and + shape update requests. + */ + LayerShapeMap maAllShapes; + + /** Set of shapes that have requested an update + + When a shape is member of this set, its maShapes entry + has bNeedsUpdate set to true. We maintain this + redundant information for faster update processing. + */ + ::std::set< ShapeSharedPtr > + maUpdateShapes; + + /// Number of shape sprites currently active on this LayerManager + sal_Int32 mnActiveSprites; + + /// sal_True, if shapes might need to move to different layer + bool mbLayerAssociationDirty; + + /// sal_False when deactivated + bool mbActive; + + /** When true, all sprite animations run in the foreground. That + is, no extra layers are created, and the slideshow runs + potentially faster. + */ + bool mbDisableAnimationZOrder; + }; + + typedef ::std::shared_ptr< LayerManager > LayerManagerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYERMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/shapemanagerimpl.cxx b/slideshow/source/engine/slide/shapemanagerimpl.cxx new file mode 100644 index 000000000..1c730970e --- /dev/null +++ b/slideshow/source/engine/slide/shapemanagerimpl.cxx @@ -0,0 +1,426 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <svx/ImageMapInfo.hxx> + +#include "shapemanagerimpl.hxx" + +#include <functional> + +using namespace css; +using namespace css::uno; +using namespace css::drawing; +using namespace css::system; + +namespace slideshow::internal { + +ShapeManagerImpl::ShapeManagerImpl( EventMultiplexer& rMultiplexer, + LayerManagerSharedPtr const& rLayerManager, + CursorManager& rCursorManager, + const ShapeEventListenerMap& rGlobalListenersMap, + const ShapeCursorMap& rGlobalCursorMap, + const Reference<XDrawPage>& xDrawPage ): + mrMultiplexer(rMultiplexer), + mpLayerManager(rLayerManager), + mrCursorManager(rCursorManager), + mrGlobalListenersMap(rGlobalListenersMap), + mrGlobalCursorMap(rGlobalCursorMap), + maShapeListenerMap(), + maShapeCursorMap(), + maHyperlinkShapes(), + mbEnabled(false), + mxDrawPage(xDrawPage) +{ +} + +void ShapeManagerImpl::activate() +{ + if( mbEnabled ) + return; + + mbEnabled = true; + + // register this handler on EventMultiplexer. + // Higher prio (overrides other engine handlers) + mrMultiplexer.addMouseMoveHandler( shared_from_this(), 2.0 ); + mrMultiplexer.addClickHandler( shared_from_this(), 2.0 ); + mrMultiplexer.addShapeListenerHandler( shared_from_this() ); + + // clone listener map + for( const auto& rListener : mrGlobalListenersMap ) + listenerAdded( rListener.first ); + + // clone cursor map + for( const auto& rListener : mrGlobalCursorMap ) + cursorChanged( rListener.first, rListener.second ); + + if( mpLayerManager ) + mpLayerManager->activate(); +} + +void ShapeManagerImpl::deactivate() +{ + if( !mbEnabled ) + return; + + mbEnabled = false; + + if( mpLayerManager ) + mpLayerManager->deactivate(); + + maShapeListenerMap.clear(); + maShapeCursorMap.clear(); + + mrMultiplexer.removeShapeListenerHandler( shared_from_this() ); + mrMultiplexer.removeMouseMoveHandler( shared_from_this() ); + mrMultiplexer.removeClickHandler( shared_from_this() ); +} + +void ShapeManagerImpl::dispose() +{ + // remove listeners (EventMultiplexer holds shared_ptr on us) + deactivate(); + + maHyperlinkShapes.clear(); + maShapeCursorMap.clear(); + maShapeListenerMap.clear(); + mpLayerManager.reset(); +} + +bool ShapeManagerImpl::handleMousePressed( awt::MouseEvent const& ) +{ + // not used here + return false; // did not handle the event +} + +bool ShapeManagerImpl::handleMouseReleased( awt::MouseEvent const& e ) +{ + if( !mbEnabled || e.Buttons != awt::MouseButton::LEFT) + return false; + + basegfx::B2DPoint const aPosition( e.X, e.Y ); + + // first check for hyperlinks, because these have + // highest prio: + OUString const hyperlink( checkForHyperlink(aPosition) ); + if( !hyperlink.isEmpty() ) + { + mrMultiplexer.notifyHyperlinkClicked(hyperlink); + return true; // event consumed + } + + // tdf#74045 Handle ImageMaps + OUString const imageMapLink(checkForImageMap(e)); + if (!imageMapLink.isEmpty()) + { + Reference<XSystemShellExecute> exec( + SystemShellExecute::create(comphelper::getProcessComponentContext())); + exec->execute(imageMapLink, OUString(), SystemShellExecuteFlags::URIS_ONLY); + + return true; + } + + // find matching shape (scan reversely, to coarsely match + // paint order) + auto aCurrBroadcaster = std::find_if(maShapeListenerMap.rbegin(), maShapeListenerMap.rend(), + [&aPosition](const ShapeToListenersMap::value_type& rBroadcaster) { + // TODO(F2): Get proper geometry polygon from the + // shape, to avoid having areas outside the shape + // react on the mouse + return rBroadcaster.first->getBounds().isInside( aPosition ) + && rBroadcaster.first->isVisible(); + }); + if (aCurrBroadcaster != maShapeListenerMap.rend()) + { + // shape hit, and shape is visible. Raise + // event. + + std::shared_ptr<comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener>> const & pCont = + aCurrBroadcaster->second; + uno::Reference<drawing::XShape> const xShape( + aCurrBroadcaster->first->getXShape() ); + + // DON'T do anything with /this/ after this point! + pCont->forEach( + [&xShape, &e]( const uno::Reference< presentation::XShapeEventListener >& rListener ) + { return rListener->click( xShape, e ); } ); + + return true; // handled this event + } + + return false; // did not handle this event +} + +bool ShapeManagerImpl::handleMouseDragged( const awt::MouseEvent& ) +{ + // not used here + return false; // did not handle the event +} + +bool ShapeManagerImpl::handleMouseMoved( const awt::MouseEvent& e ) +{ + if( !mbEnabled ) + return false; + + // find hit shape in map + const ::basegfx::B2DPoint aPosition( e.X, e.Y ); + sal_Int16 nNewCursor(-1); + + if( !checkForHyperlink(aPosition).isEmpty() || !checkForImageMap(e).isEmpty() ) + { + nNewCursor = awt::SystemPointer::REFHAND; + } + else + { + // find matching shape (scan reversely, to coarsely match + // paint order) + auto aCurrCursor = std::find_if(maShapeCursorMap.rbegin(), maShapeCursorMap.rend(), + [&aPosition](const ShapeToCursorMap::value_type& rCursor) { + // TODO(F2): Get proper geometry polygon from the + // shape, to avoid having areas outside the shape + // react on the mouse + return rCursor.first->getBounds().isInside( aPosition ) + && rCursor.first->isVisible(); + }); + if (aCurrCursor != maShapeCursorMap.rend()) + { + // shape found, and it's visible. set + // requested cursor to shape's + nNewCursor = aCurrCursor->second; + } + } + + if( nNewCursor == -1 ) + mrCursorManager.resetCursor(); + else + mrCursorManager.requestCursor( nNewCursor ); + + return false; // we don't /eat/ this event. Lower prio + // handler should see it, too. +} + +bool ShapeManagerImpl::update() +{ + if( mbEnabled && mpLayerManager ) + return mpLayerManager->update(); + + return false; +} + +bool ShapeManagerImpl::needsUpdate() const +{ + if( mbEnabled && mpLayerManager ) + return mpLayerManager->isUpdatePending(); + + return false; +} + +void ShapeManagerImpl::enterAnimationMode( const AnimatableShapeSharedPtr& rShape ) +{ + if( mbEnabled && mpLayerManager ) + mpLayerManager->enterAnimationMode(rShape); +} + +void ShapeManagerImpl::leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ) +{ + if( mbEnabled && mpLayerManager ) + mpLayerManager->leaveAnimationMode(rShape); +} + +void ShapeManagerImpl::notifyShapeUpdate( const ShapeSharedPtr& rShape ) +{ + if( mbEnabled && mpLayerManager ) + mpLayerManager->notifyShapeUpdate(rShape); +} + +ShapeSharedPtr ShapeManagerImpl::lookupShape( uno::Reference< drawing::XShape > const & xShape ) const +{ + if( mpLayerManager ) + return mpLayerManager->lookupShape(xShape); + + return ShapeSharedPtr(); +} + +const XShapeToShapeMap& ShapeManagerImpl::getXShapeToShapeMap() const +{ + assert( mpLayerManager ); + return mpLayerManager->getXShapeToShapeMap(); +} + +void ShapeManagerImpl::addHyperlinkArea( const HyperlinkAreaSharedPtr& rArea ) +{ + maHyperlinkShapes.insert(rArea); +} + +AttributableShapeSharedPtr ShapeManagerImpl::getSubsetShape( const AttributableShapeSharedPtr& rOrigShape, + const DocTreeNode& rTreeNode ) +{ + if( mpLayerManager ) + return mpLayerManager->getSubsetShape(rOrigShape,rTreeNode); + + return AttributableShapeSharedPtr(); +} + +void ShapeManagerImpl::revokeSubset( const AttributableShapeSharedPtr& rOrigShape, + const AttributableShapeSharedPtr& rSubsetShape ) +{ + if( mpLayerManager ) + mpLayerManager->revokeSubset(rOrigShape,rSubsetShape); +} + +bool ShapeManagerImpl::listenerAdded( + const uno::Reference<drawing::XShape>& xShape ) +{ + ShapeEventListenerMap::const_iterator aIter = mrGlobalListenersMap.find( xShape ); + if( aIter == mrGlobalListenersMap.end() ) + { + ENSURE_OR_RETURN_FALSE(false, + "ShapeManagerImpl::listenerAdded(): global " + "shape listener map inconsistency!"); + } + + // is this one of our shapes? other shapes are ignored. + ShapeSharedPtr pShape( lookupShape(xShape) ); + if( pShape ) + { + maShapeListenerMap.emplace(pShape, aIter->second); + } + + return true; +} + +bool ShapeManagerImpl::listenerRemoved( const uno::Reference<drawing::XShape>& xShape ) +{ + // shape really erased from map? maybe there are other listeners + // for the same shape pending... + if( mrGlobalListenersMap.find(xShape) == mrGlobalListenersMap.end() ) + { + // is this one of our shapes? other shapes are ignored. + ShapeSharedPtr pShape( lookupShape(xShape) ); + if( pShape ) + maShapeListenerMap.erase(pShape); + } + + return true; +} + +void ShapeManagerImpl::cursorChanged( const uno::Reference<drawing::XShape>& xShape, + sal_Int16 nCursor ) +{ + ShapeSharedPtr pShape( lookupShape(xShape) ); + + // is this one of our shapes? other shapes are ignored. + if( !pShape ) + return; + + if( mrGlobalCursorMap.find(xShape) == mrGlobalCursorMap.end() ) + { + // erased from global map - erase locally, too + maShapeCursorMap.erase(pShape); + } + else + { + // included in global map - update local one + ShapeToCursorMap::iterator aIter; + if( (aIter = maShapeCursorMap.find(pShape)) + == maShapeCursorMap.end() ) + { + maShapeCursorMap.emplace(pShape, nCursor); + } + else + { + aIter->second = nCursor; + } + } +} + +OUString ShapeManagerImpl::checkForHyperlink( basegfx::B2DPoint const& hitPos ) const +{ + // find matching region (scan reversely, to coarsely match + // paint order): set is ordered by priority + AreaSet::const_reverse_iterator iPos( maHyperlinkShapes.rbegin() ); + AreaSet::const_reverse_iterator const iEnd( maHyperlinkShapes.rend() ); + for( ; iPos != iEnd; ++iPos ) + { + HyperlinkAreaSharedPtr const& pArea = *iPos; + + HyperlinkArea::HyperlinkRegions const linkRegions( + pArea->getHyperlinkRegions() ); + + for( std::size_t i = linkRegions.size(); i--; ) + { + basegfx::B2DRange const& region = linkRegions[i].first; + if( region.isInside(hitPos) ) + return linkRegions[i].second; + } + } + + return OUString(); +} + +OUString ShapeManagerImpl::checkForImageMap( awt::MouseEvent const& evt ) const +{ + for (sal_Int32 i = 0; i < mxDrawPage->getCount(); i++) + { + Reference<XShape> xShape(mxDrawPage->getByIndex(i), UNO_QUERY_THROW); + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape); + if (!pObj) + continue; + const IMapObject* pIMapObj = SvxIMapInfo::GetHitIMapObject(pObj, Point(evt.X, evt.Y)); + if (pIMapObj && !pIMapObj->GetURL().isEmpty()) + { + return pIMapObj->GetURL(); + } + } + return OUString(); +} + +void ShapeManagerImpl::addIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) +{ + maIntrinsicAnimationEventHandlers.add( rHandler ); +} + +void ShapeManagerImpl::removeIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) +{ + maIntrinsicAnimationEventHandlers.remove( rHandler ); +} + +void ShapeManagerImpl::notifyIntrinsicAnimationsEnabled() +{ + maIntrinsicAnimationEventHandlers.applyAll( + std::mem_fn(&IntrinsicAnimationEventHandler::enableAnimations)); +} + +void ShapeManagerImpl::notifyIntrinsicAnimationsDisabled() +{ + maIntrinsicAnimationEventHandlers.applyAll( + std::mem_fn(&IntrinsicAnimationEventHandler::disableAnimations)); +} + + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/shapemanagerimpl.hxx b/slideshow/source/engine/slide/shapemanagerimpl.hxx new file mode 100644 index 000000000..20bbe0340 --- /dev/null +++ b/slideshow/source/engine/slide/shapemanagerimpl.hxx @@ -0,0 +1,189 @@ +/* -*- 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_SLIDE_SHAPEMANAGERIMPL_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SHAPEMANAGERIMPL_HXX + +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/uno/Reference.hxx> + +#include <comphelper/interfacecontainer3.hxx> +#include <shape.hxx> +#include <subsettableshapemanager.hxx> +#include <eventmultiplexer.hxx> +#include "layermanager.hxx" +#include <viewupdate.hxx> +#include <shapemaps.hxx> +#include <cursormanager.hxx> +#include <hyperlinkarea.hxx> +#include <listenercontainer.hxx> +#include <shapelistenereventhandler.hxx> +#include <mouseeventhandler.hxx> + +#include <set> +#include <map> +#include <memory> + +namespace slideshow::internal { + +/** Listener class for shape events + + This helper class registers itself on each view, and + broadcasts the XShapeEventListener events. The mouse motion + events are needed for setting the shape cursor. +*/ +class ShapeManagerImpl : public SubsettableShapeManager, + public ShapeListenerEventHandler, + public MouseEventHandler, + public ViewUpdate, + public std::enable_shared_from_this<ShapeManagerImpl> +{ +public: + /** Create a shape event broadcaster + + @param rEventMultiplexer + The slideshow-global event source, where this class + registers its event handlers. + */ + ShapeManagerImpl( EventMultiplexer& rMultiplexer, + LayerManagerSharedPtr const& rLayerManager, + CursorManager& rCursorManager, + const ShapeEventListenerMap& rGlobalListenersMap, + const ShapeCursorMap& rGlobalCursorMap, + const css::uno::Reference<css::drawing::XDrawPage>& xDrawPage); + + /// Forbid copy construction + ShapeManagerImpl(const ShapeManagerImpl&) = delete; + + /// Forbid copy assignment + ShapeManagerImpl& operator=(const ShapeManagerImpl&) = delete; + + /** Enables event listening. + + The initial slide content on the background layer + is already rendered (e.g. from a previous slide + transition). + */ + void activate(); + + /** Disables event listening. + */ + void deactivate(); + + // Disposable interface + + + virtual void dispose() override; + +private: + + // MouseEventHandler interface + + + virtual bool handleMousePressed( + css::awt::MouseEvent const& evt ) override; + virtual bool handleMouseReleased( + css::awt::MouseEvent const& evt ) override; + virtual bool handleMouseDragged( + css::awt::MouseEvent const& evt ) override; + virtual bool handleMouseMoved( + css::awt::MouseEvent const& evt ) override; + + + // ViewUpdate interface + + + virtual bool update() override; + virtual bool needsUpdate() const override; + + + // ShapeManager interface + + + virtual void enterAnimationMode( const AnimatableShapeSharedPtr& rShape ) override; + virtual void leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ) override; + virtual void notifyShapeUpdate( const ShapeSharedPtr& rShape ) override; + virtual ShapeSharedPtr lookupShape( + css::uno::Reference< css::drawing::XShape > const & xShape ) const override; + virtual const XShapeToShapeMap& getXShapeToShapeMap() const override; + virtual void addHyperlinkArea( const HyperlinkAreaSharedPtr& rArea ) override; + + + // SubsettableShapeManager interface + + + virtual AttributableShapeSharedPtr getSubsetShape( + const AttributableShapeSharedPtr& rOrigShape, + const DocTreeNode& rTreeNode ) override; + virtual void revokeSubset( + const AttributableShapeSharedPtr& rOrigShape, + const AttributableShapeSharedPtr& rSubsetShape ) override; + + virtual void addIntrinsicAnimationHandler( + const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) override; + virtual void removeIntrinsicAnimationHandler( + const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) override; + virtual void notifyIntrinsicAnimationsEnabled() override; + virtual void notifyIntrinsicAnimationsDisabled() override; + + + // ShapeListenerEventHandler + + + virtual bool listenerAdded( const css::uno::Reference< css::drawing::XShape>& xShape ) override; + + virtual bool listenerRemoved( const css::uno::Reference< css::drawing::XShape>& xShape ) override; + + void cursorChanged( const css::uno::Reference< css::drawing::XShape>& xShape, + sal_Int16 nCursor ); + + + OUString checkForHyperlink( ::basegfx::B2DPoint const& hitPos )const; + OUString checkForImageMap( css::awt::MouseEvent const& evt ) const; + + + typedef std::map<ShapeSharedPtr, + std::shared_ptr< ::comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener> >, + Shape::lessThanShape> ShapeToListenersMap; + typedef std::map<ShapeSharedPtr, sal_Int16, + Shape::lessThanShape> ShapeToCursorMap; + typedef std::set<HyperlinkAreaSharedPtr, + HyperlinkArea::lessThanArea> AreaSet; + + typedef ThreadUnsafeListenerContainer< + IntrinsicAnimationEventHandlerSharedPtr, + std::vector<IntrinsicAnimationEventHandlerSharedPtr> > ImplIntrinsicAnimationEventHandlers; + + EventMultiplexer& mrMultiplexer; + LayerManagerSharedPtr mpLayerManager; + CursorManager& mrCursorManager; + const ShapeEventListenerMap& mrGlobalListenersMap; + const ShapeCursorMap& mrGlobalCursorMap; + ShapeToListenersMap maShapeListenerMap; + ShapeToCursorMap maShapeCursorMap; + AreaSet maHyperlinkShapes; + ImplIntrinsicAnimationEventHandlers maIntrinsicAnimationEventHandlers; + bool mbEnabled; + const css::uno::Reference<css::drawing::XDrawPage> mxDrawPage; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SHAPEMANAGERIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/slideanimations.cxx b/slideshow/source/engine/slide/slideanimations.cxx new file mode 100644 index 000000000..d433b7af1 --- /dev/null +++ b/slideshow/source/engine/slide/slideanimations.cxx @@ -0,0 +1,106 @@ +/* -*- 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 "slideanimations.hxx" +#include <animationnodefactory.hxx> + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + SlideAnimations::SlideAnimations( const SlideShowContext& rContext, + const ::basegfx::B2DVector& rSlideSize ) : + maContext( rContext ), + maSlideSize( rSlideSize ), + mpRootNode() + { + ENSURE_OR_THROW( maContext.mpSubsettableShapeManager, + "SlideAnimations::SlideAnimations(): Invalid SlideShowContext" ); + } + + SlideAnimations::~SlideAnimations() COVERITY_NOEXCEPT_FALSE + { + if( !mpRootNode ) + return; + + SHOW_NODE_TREE( mpRootNode ); + + try + { + mpRootNode->dispose(); + } + catch (uno::Exception &) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + + bool SlideAnimations::importAnimations( const uno::Reference< animations::XAnimationNode >& xRootAnimationNode ) + { + mpRootNode = AnimationNodeFactory::createAnimationNode( + xRootAnimationNode, + maSlideSize, + maContext ); + + SHOW_NODE_TREE( mpRootNode ); + + return static_cast< bool >(mpRootNode); + } + + bool SlideAnimations::isAnimated() const + { + if( !mpRootNode ) + return false; // no animations there + + // query root node about pending animations + return mpRootNode->hasPendingAnimation(); + } + + bool SlideAnimations::start() + { + if( !mpRootNode ) + return false; // no animations there + + // init all nodes + if( !mpRootNode->init() ) + return false; + + // resolve root node + if( !mpRootNode->resolve() ) + return false; + + return true; + } + + void SlideAnimations::end() + { + if( !mpRootNode ) + return; // no animations there + + // end root node + mpRootNode->deactivate(); + mpRootNode->end(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/slideanimations.hxx b/slideshow/source/engine/slide/slideanimations.hxx new file mode 100644 index 000000000..3ebc1b21b --- /dev/null +++ b/slideshow/source/engine/slide/slideanimations.hxx @@ -0,0 +1,107 @@ +/* -*- 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_SLIDE_SLIDEANIMATIONS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SLIDEANIMATIONS_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <basegfx/vector/b2dvector.hxx> + +#include <slideshowcontext.hxx> +#include <animationnode.hxx> + +namespace com::sun::star::animations { class XAnimationNode; } + + +/* Definition of SlideAnimations class */ + +namespace slideshow::internal + { + /** This class generates and manages all animations of a slide. + + Provided with the root animation node, this class imports + the effect information and builds the event tree for all + of the slide's animations. + */ + class SlideAnimations + { + public: + /** Create an animation generator. + + @param rContext + Slide show context, passing on common parameters + */ + SlideAnimations( const SlideShowContext& rContext, + const ::basegfx::B2DVector& rSlideSize ); + ~SlideAnimations() COVERITY_NOEXCEPT_FALSE; + + /** Import animations from a SMIL root animation node. + + This method might take some time, depending on the + complexity of the SMIL animation network to be + imported. + + @param xRootAnimationNode + Root animation node for the effects to be + generated. This is typically obtained from the + XDrawPage's XAnimationNodeSupplier. + + */ + bool importAnimations( const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode ); + + /** Check, whether imported animations actually contain + any effects. + + @return true, if there are actual animations in the + imported node tree. + */ + bool isAnimated() const; + + /** Start the animations. + + This method creates the network of events and + activities for all animations. The events and + activities are inserted into the constructor-provided + queues. These queues are not explicitly cleared, if + you rely on this object's effects to run without + interference, you should clear the queues by yourself. + + @return true, if all events have been successfully + created. + */ + bool start(); + + /** End all animations. + + This method force-ends all animations. If a slide end + event has been registered, that is fired, too. + */ + void end(); + + private: + SlideShowContext maContext; + const basegfx::B2DVector maSlideSize; + AnimationNodeSharedPtr mpRootNode; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SLIDEANIMATIONS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/slideimpl.cxx b/slideshow/source/engine/slide/slideimpl.cxx new file mode 100644 index 000000000..033af8756 --- /dev/null +++ b/slideshow/source/engine/slide/slideimpl.cxx @@ -0,0 +1,1130 @@ +/* -*- 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 <osl/diagnose.hxx> +#include <tools/diagnose_ex.h> +#include <cppcanvas/basegfxfactory.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> + +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/drawing/XMasterPageTarget.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <com/sun/star/presentation/EffectNodeType.hpp> + +#include <slide.hxx> +#include <slideshowcontext.hxx> +#include "slideanimations.hxx" +#include <doctreenode.hxx> +#include <screenupdater.hxx> +#include <cursormanager.hxx> +#include <shapeimporter.hxx> +#include <slideshowexceptions.hxx> +#include <eventqueue.hxx> +#include <activitiesqueue.hxx> +#include "layermanager.hxx" +#include "shapemanagerimpl.hxx" +#include <usereventqueue.hxx> +#include "userpaintoverlay.hxx" +#include "targetpropertiescreator.hxx" +#include <tools.hxx> +#include <box2dtools.hxx> +#include <vcl/graphicfilter.hxx> +#include <svx/svdograf.hxx> + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ +namespace +{ + +class SlideImpl : public Slide, + public CursorManager, + public ViewEventHandler, + public ::osl::DebugBase<SlideImpl> +{ +public: + SlideImpl( const uno::Reference<drawing::XDrawPage>& xDrawPage, + const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages, + const uno::Reference<animations::XAnimationNode>& xRootNode, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const uno::Reference<uno::XComponentContext>& xContext, + const ShapeEventListenerMap& rShapeListenerMap, + const ShapeCursorMap& rShapeCursorMap, + PolyPolygonVector&& rPolyPolygonVector, + RGBColor const& rUserPaintColor, + double dUserPaintStrokeWidth, + bool bUserPaintEnabled, + bool bIntrinsicAnimationsAllowed, + bool bDisableAnimationZOrder ); + + virtual ~SlideImpl() override; + + + // Slide interface + + + virtual void prefetch() override; + virtual void show( bool ) override; + virtual void hide() override; + + virtual basegfx::B2ISize getSlideSize() const override; + virtual uno::Reference<drawing::XDrawPage > getXDrawPage() const override; + virtual uno::Reference<animations::XAnimationNode> getXAnimationNode() const override; + virtual PolyPolygonVector getPolygons() override; + virtual void drawPolygons() const override; + virtual bool isPaintOverlayActive() const override; + virtual void enablePaintOverlay() override; + virtual void update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth ) override; + + + // TODO(F2): Rework SlideBitmap to no longer be based on XBitmap, + // but on canvas-independent basegfx bitmaps + virtual SlideBitmapSharedPtr getCurrentSlideBitmap( const UnoViewSharedPtr& rView ) const override; + + +private: + // ViewEventHandler + virtual void viewAdded( const UnoViewSharedPtr& rView ) override; + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override; + virtual void viewChanged( const UnoViewSharedPtr& rView ) override; + virtual void viewsChanged() override; + + // CursorManager + virtual bool requestCursor( sal_Int16 nCursorShape ) override; + virtual void resetCursor() override; + + void activatePaintOverlay(); + void deactivatePaintOverlay(); + + /** Query whether the slide has animations at all + + If the slide doesn't have animations, show() displays + only static content. If an event is registered with + registerSlideEndEvent(), this event will be + immediately activated at the end of the show() method. + + @return true, if this slide has animations, false + otherwise + */ + bool isAnimated(); + + /// Set all Shapes to their initial attributes for slideshow + bool applyInitialShapeAttributes( const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode ); + + /// Set shapes to attributes corresponding to initial or final state of slide + void applyShapeAttributes( + const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode, + bool bInitial) const; + + /// Renders current slide content to bitmap + SlideBitmapSharedPtr createCurrentSlideBitmap( + const UnoViewSharedPtr& rView, + ::basegfx::B2ISize const & rSlideSize ) const; + + /// Prefetch all shapes (not the animations) + bool loadShapes(); + + /// Retrieve slide size from XDrawPage + basegfx::B2ISize getSlideSizeImpl() const; + + /// Prefetch show, but don't call applyInitialShapeAttributes() + bool implPrefetchShow(); + + /// Add Polygons to the member maPolygons + void addPolygons(const PolyPolygonVector& rPolygons); + + // Types + // ===== + + enum SlideAnimationState + { + CONSTRUCTING_STATE=0, + INITIAL_STATE=1, + SHOWING_STATE=2, + FINAL_STATE=3, + SlideAnimationState_NUM_ENTRIES=4 + }; + + typedef std::vector< SlideBitmapSharedPtr > VectorOfSlideBitmaps; + /** Vector of slide bitmaps. + + Since the bitmap content is sensitive to animation + effects, we have an inner vector containing a distinct + bitmap for each of the SlideAnimationStates. + */ + typedef ::std::vector< std::pair< UnoViewSharedPtr, + VectorOfSlideBitmaps > > VectorOfVectorOfSlideBitmaps; + + + // Member variables + // ================ + + /// The page model object + uno::Reference< drawing::XDrawPage > mxDrawPage; + uno::Reference< drawing::XDrawPagesSupplier > mxDrawPagesSupplier; + uno::Reference< animations::XAnimationNode > mxRootNode; + + LayerManagerSharedPtr mpLayerManager; + std::shared_ptr<ShapeManagerImpl> mpShapeManager; + std::shared_ptr<SubsettableShapeManager> mpSubsettableShapeManager; + box2d::utils::Box2DWorldSharedPtr mpBox2DWorld; + + /// Contains common objects needed throughout the slideshow + SlideShowContext maContext; + + /// parent cursor manager + CursorManager& mrCursorManager; + + /// Handles the animation and event generation for us + SlideAnimations maAnimations; + PolyPolygonVector maPolygons; + + RGBColor maUserPaintColor; + double mdUserPaintStrokeWidth; + UserPaintOverlaySharedPtr mpPaintOverlay; + + /// Bitmaps with slide content at various states + mutable VectorOfVectorOfSlideBitmaps maSlideBitmaps; + + SlideAnimationState meAnimationState; + + const basegfx::B2ISize maSlideSize; + + sal_Int16 mnCurrentCursor; + + /// True, when intrinsic shape animations are allowed + bool mbIntrinsicAnimationsAllowed; + + /// True, when user paint overlay is enabled + bool mbUserPaintOverlayEnabled; + + /// True, if initial load of all page shapes succeeded + bool mbShapesLoaded; + + /// True, if initial load of all animation info succeeded + bool mbShowLoaded; + + /** True, if this slide is not static. + + If this slide has animated content, this variable will + be true, and false otherwise. + */ + bool mbHaveAnimations; + + /** True, if this slide has a main animation sequence. + + If this slide has animation content, which in turn has + a main animation sequence (which must be fully run + before EventMultiplexer::notifySlideAnimationsEnd() is + called), this member is true. + */ + bool mbMainSequenceFound; + + /// When true, show() was called. Slide hidden otherwise. + bool mbActive; + + /// When true, enablePaintOverlay was called and mbUserPaintOverlay = true + bool mbPaintOverlayActive; + + /// When true, final state attributes are already applied to shapes + bool mbFinalStateApplied; +}; + + +void slideRenderer( SlideImpl const * pSlide, const UnoViewSharedPtr& rView ) +{ + // fully clear view content to background color + rView->clearAll(); + + SlideBitmapSharedPtr pBitmap( pSlide->getCurrentSlideBitmap( rView ) ); + ::cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() ); + + const ::basegfx::B2DHomMatrix aViewTransform( rView->getTransformation() ); + const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() ); + + // setup a canvas with device coordinate space, the slide + // bitmap already has the correct dimension. + ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() ); + pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // render at given output position + pBitmap->move( aOutPosPixel ); + + // clear clip (might have been changed, e.g. from comb + // transition) + pBitmap->clip( ::basegfx::B2DPolyPolygon() ); + pBitmap->draw( pDevicePixelCanvas ); +} + + +SlideImpl::SlideImpl( const uno::Reference< drawing::XDrawPage >& xDrawPage, + const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages, + const uno::Reference< animations::XAnimationNode >& xRootNode, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const uno::Reference< uno::XComponentContext >& xComponentContext, + const ShapeEventListenerMap& rShapeListenerMap, + const ShapeCursorMap& rShapeCursorMap, + PolyPolygonVector&& rPolyPolygonVector, + RGBColor const& aUserPaintColor, + double dUserPaintStrokeWidth, + bool bUserPaintEnabled, + bool bIntrinsicAnimationsAllowed, + bool bDisableAnimationZOrder ) : + mxDrawPage( xDrawPage ), + mxDrawPagesSupplier( xDrawPages ), + mxRootNode( xRootNode ), + mpLayerManager( std::make_shared<LayerManager>( + rViewContainer, + bDisableAnimationZOrder) ), + mpShapeManager( std::make_shared<ShapeManagerImpl>( + rEventMultiplexer, + mpLayerManager, + rCursorManager, + rShapeListenerMap, + rShapeCursorMap, + xDrawPage)), + mpSubsettableShapeManager( mpShapeManager ), + mpBox2DWorld( std::make_shared<box2d::utils::box2DWorld>( + basegfx::B2DSize( getSlideSizeImpl() ) ) ), + maContext( mpSubsettableShapeManager, + rEventQueue, + rEventMultiplexer, + rScreenUpdater, + rActivitiesQueue, + rUserEventQueue, + *this, + rMediaFileManager, + rViewContainer, + xComponentContext, + mpBox2DWorld ), + mrCursorManager( rCursorManager ), + maAnimations( maContext, + basegfx::B2DSize( getSlideSizeImpl() ) ), + maPolygons(std::move(rPolyPolygonVector)), + maUserPaintColor(aUserPaintColor), + mdUserPaintStrokeWidth(dUserPaintStrokeWidth), + mpPaintOverlay(), + maSlideBitmaps(), + meAnimationState( CONSTRUCTING_STATE ), + maSlideSize(getSlideSizeImpl()), + mnCurrentCursor( awt::SystemPointer::ARROW ), + mbIntrinsicAnimationsAllowed( bIntrinsicAnimationsAllowed ), + mbUserPaintOverlayEnabled(bUserPaintEnabled), + mbShapesLoaded( false ), + mbShowLoaded( false ), + mbHaveAnimations( false ), + mbMainSequenceFound( false ), + mbActive( false ), + mbPaintOverlayActive( false ), + mbFinalStateApplied( false ) +{ + // clone already existing views for slide bitmaps + for( const auto& rView : rViewContainer ) + viewAdded( rView ); + + // register screen update (LayerManager needs to signal pending + // updates) + maContext.mrScreenUpdater.addViewUpdate(mpShapeManager); +} + +void SlideImpl::update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth ) +{ + maUserPaintColor = aUserPaintColor; + mdUserPaintStrokeWidth = dUserPaintStrokeWidth; + mbUserPaintOverlayEnabled = bUserPaintEnabled; +} + +SlideImpl::~SlideImpl() +{ + if( mpShapeManager ) + { + maContext.mrScreenUpdater.removeViewUpdate(mpShapeManager); + mpShapeManager->dispose(); + + // TODO(Q3): Make sure LayerManager (and thus Shapes) dies + // first, because SlideShowContext has SubsettableShapeManager + // as reference member. + mpLayerManager.reset(); + } +} + +void SlideImpl::prefetch() +{ + if( !mxRootNode.is() ) + return; + + // Try to prefetch all graphics from the page. This will be done + // in threads to be more efficient than loading them on-demand one by one. + std::vector<Graphic*> graphics; + for (sal_Int32 i = 0; i < mxDrawPage->getCount(); i++) + { + com::sun::star::uno::Reference<com::sun::star::drawing::XShape> xShape(mxDrawPage->getByIndex(i), com::sun::star::uno::UNO_QUERY_THROW); + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape); + if (!pObj) + continue; + if( SdrGrafObj* grafObj = dynamic_cast<SdrGrafObj*>(pObj)) + if( !grafObj->GetGraphic().isAvailable()) + graphics.push_back( const_cast<Graphic*>(&grafObj->GetGraphic())); + } + if(graphics.size() > 1) // threading does not help with loading just one + GraphicFilter::GetGraphicFilter().MakeGraphicsAvailableThreaded( graphics ); + + applyInitialShapeAttributes(mxRootNode); +} + +void SlideImpl::show( bool bSlideBackgroundPainted ) +{ + if( mbActive ) + return; // already active + + if( !mpShapeManager || !mpLayerManager ) + return; // disposed + + // set initial shape attributes (e.g. hide shapes that have + // 'appear' effect set) + if( !applyInitialShapeAttributes(mxRootNode) ) + return; + + // activate and take over view - clears view, if necessary + mbActive = true; + requestCursor( mnCurrentCursor ); + + // enable shape management & event broadcasting for shapes of this + // slide. Also enables LayerManager to record updates. Currently, + // never let LayerManager render initial slide content, use + // buffered slide bitmaps instead. + mpShapeManager->activate(); + + + // render slide to screen, if requested + if( !bSlideBackgroundPainted ) + { + for( const auto& rContext : maContext.mrViewContainer ) + slideRenderer( this, rContext ); + + maContext.mrScreenUpdater.notifyUpdate(); + } + + + // fire up animations + const bool bIsAnimated( isAnimated() ); + if( bIsAnimated ) + maAnimations.start(); // feeds initial events into queue + + // NOTE: this looks slightly weird, but is indeed correct: + // as isAnimated() might return false, _although_ there is + // a main sequence (because the animation nodes don't + // contain any executable effects), we gotta check both + // conditions here. + if( !bIsAnimated || !mbMainSequenceFound ) + { + // manually trigger a slide animation end event (we don't have + // animations at all, or we don't have a main animation + // sequence, but if we had, it'd end now). Note that having + // animations alone does not matter here, as only main + // sequence animations prevents showing the next slide on + // nextEvent(). + maContext.mrEventMultiplexer.notifySlideAnimationsEnd(); + } + + // enable shape-intrinsic animations (drawing layer animations or + // GIF animations) + if( mbIntrinsicAnimationsAllowed ) + mpSubsettableShapeManager->notifyIntrinsicAnimationsEnabled(); + + // enable paint overlay, if maUserPaintColor is valid + activatePaintOverlay(); + + + // from now on, animations might be showing + meAnimationState = SHOWING_STATE; +} + +void SlideImpl::hide() +{ + if( !mbActive || !mpShapeManager ) + return; // already hidden/disposed + + + // from now on, all animations are stopped + meAnimationState = FINAL_STATE; + + + // disable user paint overlay under all circumstances, + // this slide now ceases to be active. + deactivatePaintOverlay(); + + + // switch off all shape-intrinsic animations. + mpSubsettableShapeManager->notifyIntrinsicAnimationsDisabled(); + + // force-end all SMIL animations, too + maAnimations.end(); + + + // disable shape management & event broadcasting for shapes of this + // slide. Also disables LayerManager. + mpShapeManager->deactivate(); + + // vanish from view + resetCursor(); + mbActive = false; +} + +basegfx::B2ISize SlideImpl::getSlideSize() const +{ + return maSlideSize; +} + +uno::Reference<drawing::XDrawPage > SlideImpl::getXDrawPage() const +{ + return mxDrawPage; +} + +uno::Reference<animations::XAnimationNode> SlideImpl::getXAnimationNode() const +{ + return mxRootNode; +} + +PolyPolygonVector SlideImpl::getPolygons() +{ + if(mbPaintOverlayActive) + maPolygons = mpPaintOverlay->getPolygons(); + return maPolygons; +} + +SlideBitmapSharedPtr SlideImpl::getCurrentSlideBitmap( const UnoViewSharedPtr& rView ) const +{ + // search corresponding entry in maSlideBitmaps (which + // contains the views as the key) + VectorOfVectorOfSlideBitmaps::iterator aIter; + const VectorOfVectorOfSlideBitmaps::iterator aEnd( maSlideBitmaps.end() ); + if( (aIter=std::find_if( maSlideBitmaps.begin(), + aEnd, + [&rView] + ( const VectorOfVectorOfSlideBitmaps::value_type& cp ) + { return rView == cp.first; } ) ) == aEnd ) + { + // corresponding view not found - maybe view was not + // added to Slide? + ENSURE_OR_THROW( false, + "SlideImpl::getInitialSlideBitmap(): view does not " + "match any of the added ones" ); + } + + // ensure that the show is loaded + if( !mbShowLoaded ) + { + // only prefetch and init shapes when not done already + // (otherwise, at least applyInitialShapeAttributes() will be + // called twice for initial slide rendering). Furthermore, + // applyInitialShapeAttributes() _always_ performs + // initializations, which would be highly unwanted during a + // running show. OTOH, a slide whose mbShowLoaded is false is + // guaranteed not be running a show. + + // set initial shape attributes (e.g. hide 'appear' effect + // shapes) + if( !const_cast<SlideImpl*>(this)->applyInitialShapeAttributes( mxRootNode ) ) + ENSURE_OR_THROW(false, + "SlideImpl::getCurrentSlideBitmap(): Cannot " + "apply initial attributes"); + } + + SlideBitmapSharedPtr& rBitmap( aIter->second.at( meAnimationState )); + const ::basegfx::B2ISize& rSlideSize( + getSlideSizePixel( ::basegfx::B2DSize( getSlideSize() ), + rView )); + + // is the bitmap valid (actually existent, and of correct + // size)? + if( !rBitmap || rBitmap->getSize() != rSlideSize ) + { + // no bitmap there yet, or wrong size - create one + rBitmap = createCurrentSlideBitmap(rView, rSlideSize); + } + + return rBitmap; +} + + +// private methods + + +void SlideImpl::viewAdded( const UnoViewSharedPtr& rView ) +{ + maSlideBitmaps.emplace_back( rView, + VectorOfSlideBitmaps(SlideAnimationState_NUM_ENTRIES) ); + + if( mpLayerManager ) + mpLayerManager->viewAdded( rView ); +} + +void SlideImpl::viewRemoved( const UnoViewSharedPtr& rView ) +{ + if( mpLayerManager ) + mpLayerManager->viewRemoved( rView ); + + const VectorOfVectorOfSlideBitmaps::iterator aEnd( maSlideBitmaps.end() ); + maSlideBitmaps.erase( + std::remove_if( maSlideBitmaps.begin(), + aEnd, + [&rView] + ( const VectorOfVectorOfSlideBitmaps::value_type& cp ) + { return rView == cp.first; } ), + aEnd ); +} + +void SlideImpl::viewChanged( const UnoViewSharedPtr& rView ) +{ + // nothing to do for the Slide - getCurrentSlideBitmap() lazily + // handles bitmap resizes + if( mbActive && mpLayerManager ) + mpLayerManager->viewChanged(rView); +} + +void SlideImpl::viewsChanged() +{ + // nothing to do for the Slide - getCurrentSlideBitmap() lazily + // handles bitmap resizes + if( mbActive && mpLayerManager ) + mpLayerManager->viewsChanged(); +} + +bool SlideImpl::requestCursor( sal_Int16 nCursorShape ) +{ + mnCurrentCursor = nCursorShape; + return mrCursorManager.requestCursor(mnCurrentCursor); +} + +void SlideImpl::resetCursor() +{ + mnCurrentCursor = awt::SystemPointer::ARROW; + mrCursorManager.resetCursor(); +} + +bool SlideImpl::isAnimated() +{ + // prefetch, but don't apply initial shape attributes + if( !implPrefetchShow() ) + return false; + + return mbHaveAnimations && maAnimations.isAnimated(); +} + +SlideBitmapSharedPtr SlideImpl::createCurrentSlideBitmap( const UnoViewSharedPtr& rView, + const ::basegfx::B2ISize& rBmpSize ) const +{ + ENSURE_OR_THROW( rView && rView->getCanvas(), + "SlideImpl::createCurrentSlideBitmap(): Invalid view" ); + ENSURE_OR_THROW( mpLayerManager, + "SlideImpl::createCurrentSlideBitmap(): Invalid layer manager" ); + ENSURE_OR_THROW( mbShowLoaded, + "SlideImpl::createCurrentSlideBitmap(): No show loaded" ); + + // tdf#96083 ensure end state settings are applied to shapes once when bitmap gets re-rendered + // in that state + if(!mbFinalStateApplied && FINAL_STATE == meAnimationState && mxRootNode.is()) + { + const_cast< SlideImpl* >(this)->mbFinalStateApplied = true; + applyShapeAttributes(mxRootNode, false); + } + + ::cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() ); + + // create a bitmap of appropriate size + ::cppcanvas::BitmapSharedPtr pBitmap( + ::cppcanvas::BaseGfxFactory::createBitmap( + pCanvas, + rBmpSize ) ); + + ENSURE_OR_THROW( pBitmap, + "SlideImpl::createCurrentSlideBitmap(): Cannot create page bitmap" ); + + ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( pBitmap->getBitmapCanvas() ); + + ENSURE_OR_THROW( pBitmapCanvas, + "SlideImpl::createCurrentSlideBitmap(): Cannot create page bitmap canvas" ); + + // apply linear part of destination canvas transformation (linear means in this context: + // transformation without any translational components) + ::basegfx::B2DHomMatrix aLinearTransform( rView->getTransformation() ); + aLinearTransform.set( 0, 2, 0.0 ); + aLinearTransform.set( 1, 2, 0.0 ); + pBitmapCanvas->setTransformation( aLinearTransform ); + + // output all shapes to bitmap + initSlideBackground( pBitmapCanvas, rBmpSize ); + mpLayerManager->renderTo( pBitmapCanvas ); + + return std::make_shared<SlideBitmap>( pBitmap ); +} + +class MainSequenceSearcher +{ +public: + MainSequenceSearcher() + { + maSearchKey.Name = "node-type"; + maSearchKey.Value <<= presentation::EffectNodeType::MAIN_SEQUENCE; + } + + void operator()( const uno::Reference< animations::XAnimationNode >& xChildNode ) + { + uno::Sequence< beans::NamedValue > aUserData( xChildNode->getUserData() ); + + if( findNamedValue( aUserData, maSearchKey ) ) + { + maMainSequence = xChildNode; + } + } + + const uno::Reference< animations::XAnimationNode >& getMainSequence() const + { + return maMainSequence; + } + +private: + beans::NamedValue maSearchKey; + uno::Reference< animations::XAnimationNode > maMainSequence; +}; + +bool SlideImpl::implPrefetchShow() +{ + if( mbShowLoaded ) + return true; + + ENSURE_OR_RETURN_FALSE( mxDrawPage.is(), + "SlideImpl::implPrefetchShow(): Invalid draw page" ); + ENSURE_OR_RETURN_FALSE( mpLayerManager, + "SlideImpl::implPrefetchShow(): Invalid layer manager" ); + + // fetch desired page content + // ========================== + + if( !loadShapes() ) + return false; + + // New animations framework: import the shape effect info + // ====================================================== + + try + { + if( mxRootNode.is() ) + { + if( !maAnimations.importAnimations( mxRootNode ) ) + { + OSL_FAIL( "SlideImpl::implPrefetchShow(): have animation nodes, " + "but import animations failed." ); + + // could not import animation framework, + // _although_ some animation nodes are there - + // this is an error (not finding animations at + // all is okay - might be a static slide) + return false; + } + + // now check whether we've got a main sequence (if + // not, we must manually call + // EventMultiplexer::notifySlideAnimationsEnd() + // above, as e.g. interactive sequences alone + // don't block nextEvent() from issuing the next + // slide) + MainSequenceSearcher aSearcher; + if( for_each_childNode( mxRootNode, aSearcher ) ) + mbMainSequenceFound = aSearcher.getMainSequence().is(); + + // import successfully done + mbHaveAnimations = true; + } + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + // TODO(E2): Error handling. For now, bail out + } + + mbShowLoaded = true; + + return true; +} + +void SlideImpl::enablePaintOverlay() +{ + if( !mbUserPaintOverlayEnabled || !mbPaintOverlayActive ) + { + mbUserPaintOverlayEnabled = true; + activatePaintOverlay(); + } +} + +void SlideImpl::activatePaintOverlay() +{ + if( mbUserPaintOverlayEnabled || !maPolygons.empty() ) + { + mpPaintOverlay = UserPaintOverlay::create( maUserPaintColor, + mdUserPaintStrokeWidth, + maContext, + std::vector(maPolygons), + mbUserPaintOverlayEnabled ); + mbPaintOverlayActive = true; + } +} + +void SlideImpl::drawPolygons() const +{ + if( mpPaintOverlay ) + mpPaintOverlay->drawPolygons(); +} + +void SlideImpl::addPolygons(const PolyPolygonVector& rPolygons) +{ + maPolygons.insert(maPolygons.end(), rPolygons.begin(), rPolygons.end()); +} + +bool SlideImpl::isPaintOverlayActive() const +{ + return mbPaintOverlayActive; +} + +void SlideImpl::deactivatePaintOverlay() +{ + if(mbPaintOverlayActive) + maPolygons = mpPaintOverlay->getPolygons(); + + mpPaintOverlay.reset(); + mbPaintOverlayActive = false; +} + +void SlideImpl::applyShapeAttributes( + const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode, + bool bInitial) const +{ + const uno::Sequence< animations::TargetProperties > aProps( + TargetPropertiesCreator::createTargetProperties( xRootAnimationNode, bInitial ) ); + + // apply extracted values to our shapes + for( const auto& rProp : aProps ) + { + sal_Int16 nParaIndex( -1 ); + uno::Reference< drawing::XShape > xShape( rProp.Target, + uno::UNO_QUERY ); + + if( !xShape.is() ) + { + // not a shape target. Maybe a ParagraphTarget? + presentation::ParagraphTarget aParaTarget; + + if( rProp.Target >>= aParaTarget ) + { + // yep, ParagraphTarget found - extract shape + // and index + xShape = aParaTarget.Shape; + nParaIndex = aParaTarget.Paragraph; + } + } + + if( xShape.is() ) + { + ShapeSharedPtr pShape( mpLayerManager->lookupShape( xShape ) ); + + if( !pShape ) + { + OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): no shape found for given target" ); + continue; + } + + AttributableShapeSharedPtr pAttrShape( + ::std::dynamic_pointer_cast< AttributableShape >( pShape ) ); + + if( !pAttrShape ) + { + OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not " + "implement AttributableShape interface" ); + continue; + } + + if( nParaIndex != -1 ) + { + // our target is a paragraph subset, thus look + // this up first. + const DocTreeNodeSupplier& rNodeSupplier( pAttrShape->getTreeNodeSupplier() ); + + if( rNodeSupplier.getNumberOfTreeNodes( + DocTreeNode::NodeType::LogicalParagraph ) <= nParaIndex ) + { + OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not " + "provide a subset for requested paragraph index" ); + continue; + } + + pAttrShape = pAttrShape->getSubset( + rNodeSupplier.getTreeNode( + nParaIndex, + DocTreeNode::NodeType::LogicalParagraph ) ); + + if( !pAttrShape ) + { + OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not " + "provide a subset for requested paragraph index" ); + continue; + } + } + + const uno::Sequence< beans::NamedValue >& rShapeProps( rProp.Properties ); + for( const auto& rShapeProp : rShapeProps ) + { + bool bVisible=false; + if( rShapeProp.Name.equalsIgnoreAsciiCase("visibility") && + extractValue( bVisible, + rShapeProp.Value, + pShape, + ::basegfx::B2DSize( getSlideSize() ) )) + { + pAttrShape->setVisibility( bVisible ); + } + else + { + OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): Unexpected " + "(and unimplemented) property encountered" ); + } + } + } + } +} + +bool SlideImpl::applyInitialShapeAttributes( + const uno::Reference< animations::XAnimationNode >& xRootAnimationNode ) +{ + if( !implPrefetchShow() ) + return false; + + if( !xRootAnimationNode.is() ) + { + meAnimationState = INITIAL_STATE; + + return true; // no animations - no attributes to apply - + // succeeded + } + + applyShapeAttributes(xRootAnimationNode, true); + + meAnimationState = INITIAL_STATE; + + return true; +} + +bool SlideImpl::loadShapes() +{ + if( mbShapesLoaded ) + return true; + + ENSURE_OR_RETURN_FALSE( mxDrawPage.is(), + "SlideImpl::loadShapes(): Invalid draw page" ); + ENSURE_OR_RETURN_FALSE( mpLayerManager, + "SlideImpl::loadShapes(): Invalid layer manager" ); + + // fetch desired page content + // ========================== + + // also take master page content + uno::Reference< drawing::XDrawPage > xMasterPage; + uno::Reference< drawing::XShapes > xMasterPageShapes; + sal_Int32 nCurrCount(0); + + uno::Reference< drawing::XMasterPageTarget > xMasterPageTarget( mxDrawPage, + uno::UNO_QUERY ); + if( xMasterPageTarget.is() ) + { + xMasterPage = xMasterPageTarget->getMasterPage(); + xMasterPageShapes = xMasterPage; + + if( xMasterPage.is() && xMasterPageShapes.is() ) + { + // TODO(P2): maybe cache master pages here (or treat the + // masterpage as a single metafile. At least currently, + // masterpages do not contain animation effects) + try + { + // load the masterpage shapes + + ShapeImporter aMPShapesFunctor( xMasterPage, + mxDrawPage, + mxDrawPagesSupplier, + maContext, + 0, /* shape num starts at 0 */ + true ); + + mpLayerManager->addShape( + aMPShapesFunctor.importBackgroundShape() ); + + while( !aMPShapesFunctor.isImportDone() ) + { + ShapeSharedPtr const& rShape( + aMPShapesFunctor.importShape() ); + if( rShape ) + { + rShape->setIsForeground(false); + mpLayerManager->addShape( rShape ); + } + } + addPolygons(aMPShapesFunctor.getPolygons()); + + nCurrCount = static_cast<sal_Int32>(aMPShapesFunctor.getImportedShapesCount()); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( ShapeLoadFailedException& ) + { + // TODO(E2): Error handling. For now, bail out + TOOLS_WARN_EXCEPTION( "slideshow", "SlideImpl::loadShapes(): caught ShapeLoadFailedException" ); + return false; + + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + return false; + } + } + } + + try + { + // load the normal page shapes + + + ShapeImporter aShapesFunctor( mxDrawPage, + mxDrawPage, + mxDrawPagesSupplier, + maContext, + nCurrCount, + false ); + + while( !aShapesFunctor.isImportDone() ) + { + ShapeSharedPtr const& rShape( + aShapesFunctor.importShape() ); + if( rShape ) + mpLayerManager->addShape( rShape ); + } + addPolygons(aShapesFunctor.getPolygons()); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( ShapeLoadFailedException& ) + { + // TODO(E2): Error handling. For now, bail out + TOOLS_WARN_EXCEPTION( "slideshow", "SlideImpl::loadShapes(): caught ShapeLoadFailedException" ); + return false; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + return false; + } + + mbShapesLoaded = true; + + return true; +} + +basegfx::B2ISize SlideImpl::getSlideSizeImpl() const +{ + uno::Reference< beans::XPropertySet > xPropSet( + mxDrawPage, uno::UNO_QUERY_THROW ); + + sal_Int32 nDocWidth = 0; + sal_Int32 nDocHeight = 0; + xPropSet->getPropertyValue("Width") >>= nDocWidth; + xPropSet->getPropertyValue("Height") >>= nDocHeight; + + return basegfx::B2ISize( nDocWidth, nDocHeight ); +} + +} // namespace + + +SlideSharedPtr createSlide( const uno::Reference< drawing::XDrawPage >& xDrawPage, + const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages, + const uno::Reference< animations::XAnimationNode >& xRootNode, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const uno::Reference< uno::XComponentContext >& xComponentContext, + const ShapeEventListenerMap& rShapeListenerMap, + const ShapeCursorMap& rShapeCursorMap, + PolyPolygonVector&& rPolyPolygonVector, + RGBColor const& rUserPaintColor, + double dUserPaintStrokeWidth, + bool bUserPaintEnabled, + bool bIntrinsicAnimationsAllowed, + bool bDisableAnimationZOrder ) +{ + auto pRet = std::make_shared<SlideImpl>( xDrawPage, xDrawPages, xRootNode, rEventQueue, + rEventMultiplexer, rScreenUpdater, + rActivitiesQueue, rUserEventQueue, + rCursorManager, rMediaFileManager, rViewContainer, + xComponentContext, rShapeListenerMap, + rShapeCursorMap, std::move(rPolyPolygonVector), rUserPaintColor, + dUserPaintStrokeWidth, bUserPaintEnabled, + bIntrinsicAnimationsAllowed, + bDisableAnimationZOrder ); + + rEventMultiplexer.addViewHandler( pRet ); + + return pRet; +} + +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/targetpropertiescreator.cxx b/slideshow/source/engine/slide/targetpropertiescreator.cxx new file mode 100644 index 000000000..fdc45c3da --- /dev/null +++ b/slideshow/source/engine/slide/targetpropertiescreator.cxx @@ -0,0 +1,370 @@ +/* -*- 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/XIterateContainer.hpp> +#include <com/sun/star/presentation/ParagraphTarget.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/animations/AnimationNodeType.hpp> +#include <com/sun/star/animations/XAnimate.hpp> +#include <comphelper/sequence.hxx> + +#include <unordered_map> +#include <vector> + +#include "targetpropertiescreator.hxx" +#include <tools.hxx> + +namespace slideshow::internal +{ + namespace + { + // Vector containing all properties for a given shape + typedef ::std::vector< beans::NamedValue > VectorOfNamedValues; + + /** The hash map key + + This key contains both XShape reference and a paragraph + index, as we somehow have to handle shape and paragraph + targets with the same data structure. + */ + struct ShapeHashKey + { + /// Shape target + uno::Reference< drawing::XShape > mxRef; + + /** Paragraph index. + + If this is a pure shape target, mnParagraphIndex is + set to -1. + */ + sal_Int16 mnParagraphIndex; + + /// Comparison needed for unordered_map + bool operator==( const ShapeHashKey& rRHS ) const + { + return mxRef == rRHS.mxRef && mnParagraphIndex == rRHS.mnParagraphIndex; + } + }; + + // A hash functor for ShapeHashKey objects + struct ShapeKeyHasher + { + ::std::size_t operator()( const ShapeHashKey& rKey ) const + { + // TODO(P2): Maybe a better hash function would be to + // spread mnParagraphIndex to 32 bit: a0b0c0d0e0... Hakmem + // should have a formula. + + // Yes it has: + // x = (x & 0x0000FF00) << 8) | (x >> 8) & 0x0000FF00 | x & 0xFF0000FF; + // x = (x & 0x00F000F0) << 4) | (x >> 4) & 0x00F000F0 | x & 0xF00FF00F; + // x = (x & 0x0C0C0C0C) << 2) | (x >> 2) & 0x0C0C0C0C | x & 0xC3C3C3C3; + // x = (x & 0x22222222) << 1) | (x >> 1) & 0x22222222 | x & 0x99999999; + + // Costs about 17 cycles on a RISC machine with infinite + // instruction level parallelism (~42 basic + // instructions). Thus I truly doubt this pays off... + return reinterpret_cast< ::std::size_t >(rKey.mxRef.get()) ^ (rKey.mnParagraphIndex << 16); + } + }; + + // A hash map which maps a XShape to the corresponding vector of initial properties + typedef std::unordered_map< ShapeHashKey, VectorOfNamedValues, ShapeKeyHasher > XShapeToNamedValuesMap; + + + class NodeFunctor + { + public: + explicit NodeFunctor( + XShapeToNamedValuesMap& rShapeHash, + bool bInitial ) + : mrShapeHash( rShapeHash ), + mxTargetShape(), + mnParagraphIndex( -1 ), + mbInitial( bInitial) + { + } + + NodeFunctor( XShapeToNamedValuesMap& rShapeHash, + const uno::Reference< drawing::XShape >& rTargetShape, + sal_Int16 nParagraphIndex, + bool bInitial) : + mrShapeHash( rShapeHash ), + mxTargetShape( rTargetShape ), + mnParagraphIndex( nParagraphIndex ), + mbInitial( bInitial ) + { + } + + void operator()( const uno::Reference< animations::XAnimationNode >& xNode ) const + { + if( !xNode.is() ) + { + OSL_FAIL( "AnimCore: NodeFunctor::operator(): invalid XAnimationNode" ); + return; + } + + uno::Reference< drawing::XShape > xTargetShape( mxTargetShape ); + sal_Int16 nParagraphIndex( mnParagraphIndex ); + + switch( xNode->getType() ) + { + case animations::AnimationNodeType::ITERATE: + { + // extract target shape from iterate node + // (will override the target for all children) + + uno::Reference< animations::XIterateContainer > xIterNode( xNode, + uno::UNO_QUERY ); + + // TODO(E1): I'm not too sure what to expect here... + if( !xIterNode->getTarget().hasValue() ) + { + OSL_FAIL( "animcore: NodeFunctor::operator(): no target on ITERATE node" ); + return; + } + + xTargetShape.set( xIterNode->getTarget(), + uno::UNO_QUERY ); + + if( !xTargetShape.is() ) + { + css::presentation::ParagraphTarget aTarget; + + // no shape provided. Maybe a ParagraphTarget? + if( !(xIterNode->getTarget() >>= aTarget) ) + { + OSL_FAIL( "animcore: NodeFunctor::operator(): could not extract any " + "target information" ); + return; + } + + xTargetShape = aTarget.Shape; + nParagraphIndex = aTarget.Paragraph; + + if( !xTargetShape.is() ) + { + OSL_FAIL( "animcore: NodeFunctor::operator(): invalid shape in ParagraphTarget" ); + return; + } + } + [[fallthrough]]; + } + case animations::AnimationNodeType::PAR: + case animations::AnimationNodeType::SEQ: + { + /// forward bInitial + NodeFunctor aFunctor( mrShapeHash, + xTargetShape, + nParagraphIndex, + mbInitial ); + if( !for_each_childNode( xNode, aFunctor ) ) + { + OSL_FAIL( "AnimCore: NodeFunctor::operator(): child node iteration failed, " + "or extraneous container nodes encountered" ); + } + } + break; + + case animations::AnimationNodeType::CUSTOM: + case animations::AnimationNodeType::ANIMATE: + case animations::AnimationNodeType::ANIMATEMOTION: + case animations::AnimationNodeType::ANIMATECOLOR: + case animations::AnimationNodeType::ANIMATETRANSFORM: + case animations::AnimationNodeType::TRANSITIONFILTER: + case animations::AnimationNodeType::AUDIO: + /*default: + // ignore this node, no valuable content for now. + break;*/ + + case animations::AnimationNodeType::SET: + { + // evaluate set node content + uno::Reference< animations::XAnimate > xAnimateNode( xNode, + uno::UNO_QUERY ); + + if( !xAnimateNode.is() ) + break; // invalid node + + // determine target shape (if any) + ShapeHashKey aTarget; + if( xTargetShape.is() ) + { + // override target shape with parent-supplied + aTarget.mxRef = xTargetShape; + aTarget.mnParagraphIndex = nParagraphIndex; + } + else + { + // no parent-supplied target, retrieve + // node target + if( xAnimateNode->getTarget() >>= aTarget.mxRef ) + { + // pure shape target - set paragraph + // index to magic + aTarget.mnParagraphIndex = -1; + } + else + { + // not a pure shape target - maybe a + // ParagraphTarget? + presentation::ParagraphTarget aUnoTarget; + + if( !(xAnimateNode->getTarget() >>= aUnoTarget) ) + { + OSL_FAIL( "AnimCore: NodeFunctor::operator(): unknown target type encountered" ); + break; + } + + aTarget.mxRef = aUnoTarget.Shape; + aTarget.mnParagraphIndex = aUnoTarget.Paragraph; + } + } + + if( !aTarget.mxRef.is() ) + { + OSL_FAIL( "AnimCore: NodeFunctor::operator(): Found target, but XShape is NULL" ); + break; // invalid target XShape + } + + // check whether we already have an entry for + // this target (we only take the first set + // effect for every shape) - but keep going if + // we're requested the final state (which + // eventually gets overwritten in the + // unordered list, see tdf#96083) + if( mbInitial && mrShapeHash.find( aTarget ) != mrShapeHash.end() ) + break; // already an entry in existence for given XShape + + // if this is an appear effect, hide shape + // initially. This is currently the only place + // where a shape effect influences shape + // attributes outside it's effective duration. + bool bVisible( false ); + if( xAnimateNode->getAttributeName().equalsIgnoreAsciiCase("visibility") ) + { + + uno::Any aAny( xAnimateNode->getTo() ); + + // try to extract bool value + if( !(aAny >>= bVisible) ) + { + // try to extract string + OUString aString; + if( aAny >>= aString ) + { + // we also take the strings "true" and "false", + // as well as "on" and "off" here + if( aString.equalsIgnoreAsciiCase("true") || + aString.equalsIgnoreAsciiCase("on") ) + { + bVisible = true; + } + if( aString.equalsIgnoreAsciiCase("false") || + aString.equalsIgnoreAsciiCase("off") ) + { + bVisible = false; + } + } + } + } + + // if initial anim sets shape visible, set it + // to invisible. If we're asked for the final + // state, don't do anything obviously + if(mbInitial) + bVisible = !bVisible; + + // target is set the 'visible' value, + // so we should record the opposite value + mrShapeHash.emplace( + aTarget, + VectorOfNamedValues( + 1, + beans::NamedValue( + //xAnimateNode->getAttributeName(), + "visibility", + uno::Any( bVisible ) ) ) ); + break; + } + } + } + + private: + XShapeToNamedValuesMap& mrShapeHash; + uno::Reference< drawing::XShape > mxTargetShape; + sal_Int16 mnParagraphIndex; + + // get initial or final state + bool mbInitial; + }; + } + + uno::Sequence< animations::TargetProperties > TargetPropertiesCreator::createTargetProperties + ( + const uno::Reference< animations::XAnimationNode >& xRootNode, + bool bInitial + ) //throw (uno::RuntimeException, std::exception) + { + // scan all nodes for visibility changes, and record first + // 'visibility=true' for each shape + XShapeToNamedValuesMap aShapeHash( 101 ); + + NodeFunctor aFunctor( + aShapeHash, + bInitial ); + + // TODO(F1): Maybe limit functor application to main sequence + // alone (CL said something that shape visibility is only + // affected by effects in the main sequence for PPT). + + // OTOH, client code can pass us only the main sequence (which + // it actually does right now, for the slideshow implementation). + aFunctor( xRootNode ); + + // output to result sequence + uno::Sequence< animations::TargetProperties > aRes( aShapeHash.size() ); + auto aResRange = asNonConstRange(aRes); + + ::std::size_t nCurrIndex(0); + for( const auto& rIter : aShapeHash ) + { + animations::TargetProperties& rCurrProps( aResRange[ nCurrIndex++ ] ); + + if( rIter.first.mnParagraphIndex == -1 ) + { + rCurrProps.Target <<= rIter.first.mxRef; + } + else + { + rCurrProps.Target <<= + presentation::ParagraphTarget( + rIter.first.mxRef, + rIter.first.mnParagraphIndex ); + } + + rCurrProps.Properties = ::comphelper::containerToSequence( rIter.second ); + } + + return aRes; + } + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/targetpropertiescreator.hxx b/slideshow/source/engine/slide/targetpropertiescreator.hxx new file mode 100644 index 000000000..79c1e49a5 --- /dev/null +++ b/slideshow/source/engine/slide/targetpropertiescreator.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX + +#include <com/sun/star/animations/TargetProperties.hpp> +#include <com/sun/star/animations/XAnimationNode.hpp> + +using namespace ::com::sun::star; + +namespace slideshow::internal::TargetPropertiesCreator +{ + /// Generate shape property list - set bInitial to true for initial slide state + uno::Sequence< animations::TargetProperties > createTargetProperties( + const uno::Reference< animations::XAnimationNode >& rootNode, + bool bInitial ); + +} // namespace slideshow::internal::TargetPropertiesCreator + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/userpaintoverlay.cxx b/slideshow/source/engine/slide/userpaintoverlay.cxx new file mode 100644 index 000000000..d635fc60a --- /dev/null +++ b/slideshow/source/engine/slide/userpaintoverlay.cxx @@ -0,0 +1,485 @@ +/* -*- 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/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <cppcanvas/basegfxfactory.hxx> +#include <tools/diagnose_ex.h> + +#include <slideshowcontext.hxx> +#include "userpaintoverlay.hxx" +#include <mouseeventhandler.hxx> +#include <eventmultiplexer.hxx> +#include <screenupdater.hxx> +#include <vieweventhandler.hxx> + +#include <slide.hxx> +#include <cursormanager.hxx> + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + class PaintOverlayHandler : public MouseEventHandler, + public ViewEventHandler, + public UserPaintEventHandler + { + public: + PaintOverlayHandler( const RGBColor& rStrokeColor, + double nStrokeWidth, + ScreenUpdater& rScreenUpdater, + const UnoViewContainer& rViews, + Slide& rSlide, + PolyPolygonVector&& rPolygons, + bool bActive ) : + mrScreenUpdater( rScreenUpdater ), + maViews(), + maPolygons( std::move(rPolygons) ), + maStrokeColor( rStrokeColor ), + mnStrokeWidth( nStrokeWidth ), + maLastPoint(), + maLastMouseDownPos(), + mbIsLastPointValid( false ), + mbIsLastMouseDownPosValid( false ), + //handle the "remove all ink from slide" mode of erasing + mbIsEraseAllModeActivated( false ), + //handle the "remove stroke by stroke" mode of erasing + mbIsEraseModeActivated( false ), + mrSlide(rSlide), + mnSize(100), + mbActive( bActive ) + { + for( const auto& rView : rViews ) + viewAdded( rView ); + + drawPolygons(); + } + + void dispose() + { + maViews.clear(); + } + + // ViewEventHandler methods + virtual void viewAdded( const UnoViewSharedPtr& rView ) override + { + maViews.push_back( rView ); + } + + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override + { + maViews.erase( ::std::remove( maViews.begin(), + maViews.end(), + rView ) ); + } + + virtual void viewChanged( const UnoViewSharedPtr& /*rView*/ ) override + { + // TODO(F2): for persistent drawings, need to store + // polygon and repaint here. + } + + virtual void viewsChanged() override + { + // TODO(F2): for persistent drawings, need to store + // polygon and repaint here. + } + + bool colorChanged( RGBColor const& rUserColor ) override + { + mbIsLastPointValid = false; + mbActive = true; + maStrokeColor = rUserColor; + mbIsEraseModeActivated = false; + return true; + } + + bool widthChanged( double nUserStrokeWidth ) override + { + mnStrokeWidth = nUserStrokeWidth; + mbIsEraseModeActivated = false; + return true; + } + + void repaintWithoutPolygons() + { + // must get access to the instance to erase all polygon + for( const auto& rxView : maViews ) + { + // fully clear view content to background color + //rxView->getCanvas()->clear(); + + //get via SlideImpl instance the bitmap of the slide unmodified to redraw it + SlideBitmapSharedPtr pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) ); + ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() ); + + const ::basegfx::B2DHomMatrix aViewTransform( rxView->getTransformation() ); + const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() ); + + // setup a canvas with device coordinate space, the slide + // bitmap already has the correct dimension. + ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() ); + + pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // render at given output position + pBitmap->move( aOutPosPixel ); + + // clear clip (might have been changed, e.g. from comb + // transition) + pBitmap->clip( ::basegfx::B2DPolyPolygon() ); + pBitmap->draw( pDevicePixelCanvas ); + + mrScreenUpdater.notifyUpdate(rxView,true); + } + } + + bool eraseAllInkChanged( bool bEraseAllInk ) override + { + mbIsEraseAllModeActivated = bEraseAllInk; + // if the erase all mode is activated it will remove all ink from slide, + // therefore destroy all the polygons stored + if(mbIsEraseAllModeActivated) + { + // The Erase Mode should be deactivated + mbIsEraseModeActivated = false; + repaintWithoutPolygons(); + maPolygons.clear(); + } + mbIsEraseAllModeActivated=false; + return true; + } + + bool eraseInkWidthChanged( sal_Int32 rEraseInkSize ) override + { + // Change the size + mnSize=rEraseInkSize; + // Changed to mode Erase + mbIsEraseModeActivated = true; + return true; + } + + bool switchPenMode() override + { + mbIsLastPointValid = false; + mbActive = true; + mbIsEraseModeActivated = false; + return true; + } + + bool switchEraserMode() override + { + mbIsLastPointValid = false; + mbActive = true; + mbIsEraseModeActivated = true; + return true; + } + + bool disable() override + { + mbIsLastPointValid = false; + mbIsLastMouseDownPosValid = false; + mbActive = false; + return true; + } + + //Draw all registered polygons. + void drawPolygons() + { + for( const auto& rxPolygon : maPolygons ) + { + rxPolygon->draw(); + } + // screen update necessary to show painting + mrScreenUpdater.notifyUpdate(); + } + + //Retrieve all registered polygons. + const PolyPolygonVector& getPolygons() const + { + return maPolygons; + } + + // MouseEventHandler methods + virtual bool handleMousePressed( const awt::MouseEvent& e ) override + { + if( !mbActive ) + return false; + + if (e.Buttons == awt::MouseButton::RIGHT) + { + mbIsLastPointValid = false; + return false; + } + + if (e.Buttons != awt::MouseButton::LEFT) + return false; + + maLastMouseDownPos.setX( e.X ); + maLastMouseDownPos.setY( e.Y ); + mbIsLastMouseDownPosValid = true; + + // eat mouse click (though we don't process it + // _directly_, it enables the drag mode + return true; + } + + virtual bool handleMouseReleased( const awt::MouseEvent& e ) override + { + if( !mbActive ) + return false; + + if (e.Buttons == awt::MouseButton::RIGHT) + { + mbIsLastPointValid = false; + return false; + } + + if (e.Buttons != awt::MouseButton::LEFT) + return false; + + // check, whether up- and down press are on exactly + // the same pixel. If that's the case, ignore the + // click, and pass on the event to low-prio + // handlers. This effectively permits effect + // advancements via clicks also when user paint is + // enabled. + if( mbIsLastMouseDownPosValid && + ::basegfx::B2DPoint( e.X, + e.Y ) == maLastMouseDownPos ) + { + mbIsLastMouseDownPosValid = false; + return false; + } + + // invalidate, next downpress will have to start a new + // polygon. + mbIsLastPointValid = false; + + // eat mouse click (though we don't process it + // _directly_, it enables the drag mode + return true; + } + + virtual bool handleMouseDragged( const awt::MouseEvent& e ) override + { + if( !mbActive ) + return false; + + if (e.Buttons == awt::MouseButton::RIGHT) + { + mbIsLastPointValid = false; + return false; + } + + if(mbIsEraseModeActivated) + { + //define the last point as an object + //we suppose that there's no way this point could be valid + ::basegfx::B2DPolygon aPoly; + + maLastPoint.setX( e.X-mnSize ); + maLastPoint.setY( e.Y-mnSize ); + + aPoly.append( maLastPoint ); + + maLastPoint.setX( e.X-mnSize ); + maLastPoint.setY( e.Y+mnSize ); + + aPoly.append( maLastPoint ); + maLastPoint.setX( e.X+mnSize ); + maLastPoint.setY( e.Y+mnSize ); + + aPoly.append( maLastPoint ); + maLastPoint.setX( e.X+mnSize ); + maLastPoint.setY( e.Y-mnSize ); + + aPoly.append( maLastPoint ); + maLastPoint.setX( e.X-mnSize ); + maLastPoint.setY( e.Y-mnSize ); + + aPoly.append( maLastPoint ); + + //now we have defined a Polygon that is closed + + //The point is to redraw the LastPoint the way it was originally on the bitmap, + //of the slide + for (const auto& rxView : maViews) + { + + //get via SlideImpl instance the bitmap of the slide unmodified to redraw it + SlideBitmapSharedPtr pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) ); + ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() ); + + ::basegfx::B2DHomMatrix aViewTransform( rxView->getTransformation() ); + const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() ); + + // setup a canvas with device coordinate space, the slide + // bitmap already has the correct dimension. + ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() ); + + pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // render at given output position + pBitmap->move( aOutPosPixel ); + + ::basegfx::B2DPolyPolygon aPolyPoly(aPoly); + aViewTransform.translate(-aOutPosPixel.getX(), -aOutPosPixel.getY()); + aPolyPoly.transform(aViewTransform); + // set clip so that we just redraw a part of the canvas + pBitmap->clip(aPolyPoly); + pBitmap->draw( pDevicePixelCanvas ); + + mrScreenUpdater.notifyUpdate(rxView,true); + } + + } + else + { + if( !mbIsLastPointValid ) + { + mbIsLastPointValid = true; + maLastPoint.setX( e.X ); + maLastPoint.setY( e.Y ); + } + else + { + ::basegfx::B2DPolygon aPoly; + aPoly.append( maLastPoint ); + + maLastPoint.setX( e.X ); + maLastPoint.setY( e.Y ); + + aPoly.append( maLastPoint ); + + // paint to all views + for (const auto& rxView : maViews) + { + ::cppcanvas::PolyPolygonSharedPtr pPolyPoly( + ::cppcanvas::BaseGfxFactory::createPolyPolygon( rxView->getCanvas(), + aPoly ) ); + + if( pPolyPoly ) + { + pPolyPoly->setStrokeWidth(mnStrokeWidth); + pPolyPoly->setRGBALineColor( maStrokeColor.getIntegerColor() ); + pPolyPoly->draw(); + maPolygons.push_back(pPolyPoly); + } + } + + // screen update necessary to show painting + mrScreenUpdater.notifyUpdate(); + } + } + // mouse events captured + return true; + } + + virtual bool handleMouseMoved( const awt::MouseEvent& /*e*/ ) override + { + // not used here + return false; // did not handle the event + } + + private: + ScreenUpdater& mrScreenUpdater; + UnoViewVector maViews; + PolyPolygonVector maPolygons; + RGBColor maStrokeColor; + double mnStrokeWidth; + basegfx::B2DPoint maLastPoint; + basegfx::B2DPoint maLastMouseDownPos; + bool mbIsLastPointValid; + bool mbIsLastMouseDownPosValid; + // added bool for erasing purpose : + bool mbIsEraseAllModeActivated; + bool mbIsEraseModeActivated; + Slide& mrSlide; + sal_Int32 mnSize; + bool mbActive; + }; + + UserPaintOverlaySharedPtr UserPaintOverlay::create( const RGBColor& rStrokeColor, + double nStrokeWidth, + const SlideShowContext& rContext, + PolyPolygonVector&& rPolygons, + bool bActive ) + { + UserPaintOverlaySharedPtr pRet( new UserPaintOverlay( rStrokeColor, + nStrokeWidth, + rContext, + std::move(rPolygons), + bActive)); + + return pRet; + } + + UserPaintOverlay::UserPaintOverlay( const RGBColor& rStrokeColor, + double nStrokeWidth, + const SlideShowContext& rContext, + PolyPolygonVector&& rPolygons, + bool bActive ) : + mpHandler( std::make_shared<PaintOverlayHandler>( rStrokeColor, + nStrokeWidth, + rContext.mrScreenUpdater, + rContext.mrViewContainer, + //adding a link to Slide + dynamic_cast<Slide&>(rContext.mrCursorManager), + std::move(rPolygons), bActive )), + mrMultiplexer( rContext.mrEventMultiplexer ) + { + mrMultiplexer.addClickHandler( mpHandler, 3.0 ); + mrMultiplexer.addMouseMoveHandler( mpHandler, 3.0 ); + mrMultiplexer.addViewHandler( mpHandler ); + mrMultiplexer.addUserPaintHandler(mpHandler); + } + + PolyPolygonVector const & UserPaintOverlay::getPolygons() const + { + return mpHandler->getPolygons(); + } + + void UserPaintOverlay::drawPolygons() + { + mpHandler->drawPolygons(); + } + + UserPaintOverlay::~UserPaintOverlay() + { + try + { + mrMultiplexer.removeMouseMoveHandler( mpHandler ); + mrMultiplexer.removeClickHandler( mpHandler ); + mrMultiplexer.removeViewHandler( mpHandler ); + mpHandler->dispose(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slide/userpaintoverlay.hxx b/slideshow/source/engine/slide/userpaintoverlay.hxx new file mode 100644 index 000000000..ae443a0fa --- /dev/null +++ b/slideshow/source/engine/slide/userpaintoverlay.hxx @@ -0,0 +1,83 @@ +/* -*- 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_SLIDE_USERPAINTOVERLAY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_USERPAINTOVERLAY_HXX + +#include <cppcanvas/canvasgraphic.hxx> + +#include <rgbcolor.hxx> + +#include <memory> +#include <vector> + +/* Definition of UserPaintOverlay class */ + +namespace slideshow::internal + { + class EventMultiplexer; + struct SlideShowContext; + + class PaintOverlayHandler; + typedef ::std::shared_ptr< class UserPaintOverlay > UserPaintOverlaySharedPtr; + typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; + /** Slide overlay, which can be painted into by the user. + + This class registers itself at the EventMultiplexer, + listening for mouse clicks and moves. When the mouse is + dragged, a hand sketching in the selected color is shown. + */ + class UserPaintOverlay + { + public: + /** Create a UserPaintOverlay + + @param rStrokeColor + Color to use for drawing + + @param nStrokeWidth + Width of the stroked path + */ + static UserPaintOverlaySharedPtr create( const RGBColor& rStrokeColor, + double nStrokeWidth, + const SlideShowContext& rContext, + PolyPolygonVector&& rPolygons, + bool bActive); + ~UserPaintOverlay(); + UserPaintOverlay(const UserPaintOverlay&) = delete; + UserPaintOverlay& operator=(const UserPaintOverlay&) = delete; + PolyPolygonVector const & getPolygons() const; + void drawPolygons(); + + private: + UserPaintOverlay( const RGBColor& rStrokeColor, + double nStrokeWidth, + const SlideShowContext& rContext, + PolyPolygonVector&& rPolygons, + bool bActive ); + + ::std::shared_ptr<PaintOverlayHandler> mpHandler; + EventMultiplexer& mrMultiplexer; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_USERPAINTOVERLAY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slidebitmap.cxx b/slideshow/source/engine/slidebitmap.cxx new file mode 100644 index 000000000..368cb070c --- /dev/null +++ b/slideshow/source/engine/slidebitmap.cxx @@ -0,0 +1,112 @@ +/* -*- 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 <slidebitmap.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <canvas/canvastools.hxx> +#include <basegfx/utils/canvastools.hxx> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + + SlideBitmap::SlideBitmap( const ::cppcanvas::BitmapSharedPtr& rBitmap ) : + maOutputPos(), + maClipPoly(), + mxBitmap() + { + if( rBitmap ) + mxBitmap = rBitmap->getUNOBitmap(); + + ENSURE_OR_THROW( mxBitmap.is(), "SlideBitmap::SlideBitmap(): Invalid bitmap" ); + } + + bool SlideBitmap::draw( const ::cppcanvas::CanvasSharedPtr& rCanvas ) const + { + ENSURE_OR_RETURN_FALSE( rCanvas && rCanvas->getUNOCanvas().is(), + "SlideBitmap::draw(): Invalid canvas" ); + + // selectively only copy the transformation from current viewstate, + // don't want no clipping here. + rendering::ViewState aViewState; + aViewState.AffineTransform = rCanvas->getViewState().AffineTransform; + + rendering::RenderState aRenderState; + ::canvas::tools::initRenderState( aRenderState ); + + const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix(maOutputPos)); + ::canvas::tools::setRenderStateTransform( aRenderState, aTranslation ); + + try + { + if( maClipPoly.count() ) + { + // TODO(P1): Buffer the clip polygon + aRenderState.Clip = + ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + maClipPoly ); + } + + rCanvas->getUNOCanvas()->drawBitmap( mxBitmap, + aViewState, + aRenderState ); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + return false; + } + + return true; + } + + ::basegfx::B2ISize SlideBitmap::getSize() const + { + return ::basegfx::unotools::b2ISizeFromIntegerSize2D( mxBitmap->getSize() ); + } + + void SlideBitmap::move( const ::basegfx::B2DPoint& rNewPos ) + { + maOutputPos = rNewPos; + } + + void SlideBitmap::clip( const ::basegfx::B2DPolyPolygon& rClipPoly ) + { + maClipPoly = rClipPoly; + } + + const css::uno::Reference< css::rendering::XBitmap >& SlideBitmap::getXBitmap() const + { + return mxBitmap; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slideshowcontext.cxx b/slideshow/source/engine/slideshowcontext.cxx new file mode 100644 index 000000000..f0433b9d8 --- /dev/null +++ b/slideshow/source/engine/slideshowcontext.cxx @@ -0,0 +1,70 @@ +/* -*- 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/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <slideshowcontext.hxx> +#include <subsettableshapemanager.hxx> +#include <screenupdater.hxx> +#include <eventqueue.hxx> +#include <activitiesqueue.hxx> +#include <usereventqueue.hxx> +#include <eventmultiplexer.hxx> +#include <unoviewcontainer.hxx> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + +SlideShowContext::SlideShowContext( SubsettableShapeManagerSharedPtr& rSubsettableShapeManager, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const uno::Reference< + uno::XComponentContext>& rComponentContext, + box2d::utils::Box2DWorldSharedPtr& rBox2DWorldPtr ) : + mpSubsettableShapeManager( rSubsettableShapeManager ), + mrEventQueue( rEventQueue ), + mrEventMultiplexer( rEventMultiplexer ), + mrScreenUpdater( rScreenUpdater ), + mrActivitiesQueue( rActivitiesQueue ), + mrUserEventQueue( rUserEventQueue ), + mrCursorManager( rCursorManager ), + mrMediaFileManager( rMediaFileManager ), + mrViewContainer( rViewContainer ), + mxComponentContext( rComponentContext ), + mpBox2DWorld( rBox2DWorldPtr ) + {} + +void SlideShowContext::dispose() +{ + mxComponentContext.clear(); +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slideshowimpl.cxx b/slideshow/source/engine/slideshowimpl.cxx new file mode 100644 index 000000000..75b15e5c7 --- /dev/null +++ b/slideshow/source/engine/slideshowimpl.cxx @@ -0,0 +1,2439 @@ +/* -*- 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 <config_features.h> +#include <tools/diagnose_ex.h> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/supportsservice.hxx> + +#include <comphelper/interfacecontainer3.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppcanvas/polypolygon.hxx> +#include <osl/thread.hxx> + +#include <tools/debug.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/utils/canvastools.hxx> + +#include <sal/log.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/presentation/XSlideShow.hpp> +#include <com/sun/star/presentation/XSlideShowListener.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <com/sun/star/drawing/XLayer.hpp> +#include <com/sun/star/drawing/XLayerSupplier.hpp> +#include <com/sun/star/drawing/XLayerManager.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/loader/CannotActivateFactoryException.hpp> + +#include <unoviewcontainer.hxx> +#include <transitionfactory.hxx> +#include <eventmultiplexer.hxx> +#include <usereventqueue.hxx> +#include <eventqueue.hxx> +#include <cursormanager.hxx> +#include <mediafilemanager.hxx> +#include <slideshowcontext.hxx> +#include <activitiesqueue.hxx> +#include <activitiesfactory.hxx> +#include <interruptabledelayevent.hxx> +#include <slide.hxx> +#include <shapemaps.hxx> +#include <slideview.hxx> +#include <tools.hxx> +#include <unoview.hxx> +#include "rehearsetimingsactivity.hxx" +#include "waitsymbol.hxx" +#include "effectrewinder.hxx" +#include <framerate.hxx> +#include "pointersymbol.hxx" + +#include <map> +#include <vector> +#include <algorithm> + +using namespace com::sun::star; +using namespace ::slideshow::internal; + +namespace box2d::utils { class box2DWorld; + typedef ::std::shared_ptr< box2DWorld > Box2DWorldSharedPtr; } + +namespace { + +/** During animations the update() method tells its caller to call it as + soon as possible. This gives us more time to render the next frame and + still maintain a steady frame rate. This class is responsible for + synchronizing the display of new frames and thus keeping the frame rate + steady. +*/ +class FrameSynchronization +{ +public: + /** Create new object with a predefined duration between two frames. + @param nFrameDuration + The preferred duration between the display of two frames in + seconds. + */ + explicit FrameSynchronization (const double nFrameDuration); + + /** Set the current time as the time at which the current frame is + displayed. From this the target time of the next frame is derived. + */ + void MarkCurrentFrame(); + + /** When there is time left until the next frame is due then wait. + Otherwise return without delay. + */ + void Synchronize(); + + /** Activate frame synchronization when an animation is active and + frames are to be displayed in a steady rate. While active + Synchronize() will wait until the frame duration time has passed. + */ + void Activate(); + + /** Deactivate frame synchronization when no animation is active and the + time between frames depends on user actions and other external + sources. While deactivated Synchronize() will return without delay. + */ + void Deactivate(); + +private: + /** The timer that is used for synchronization is independent from the + one used by SlideShowImpl: it is not paused or modified by + animations. + */ + canvas::tools::ElapsedTime maTimer; + /** Time between the display of frames. Enforced only when mbIsActive + is <TRUE/>. + */ + const double mnFrameDuration; + /** Time (of maTimer) when the next frame shall be displayed. + Synchronize() will wait until this time. + */ + double mnNextFrameTargetTime; + /** Synchronize() will wait only when this flag is <TRUE/>. Otherwise + it returns immediately. + */ + bool mbIsActive; +}; + +/****************************************************************************** + + SlideShowImpl + + This class encapsulates the slideshow presentation viewer. + + With an instance of this class, it is possible to statically + and dynamically show a presentation, as defined by the + constructor-provided draw model (represented by a sequence + of css::drawing::XDrawPage objects). + + It is possible to show the presentation on multiple views + simultaneously (e.g. for a multi-monitor setup). Since this + class also relies on user interaction, the corresponding + XSlideShowView interface provides means to register some UI + event listeners (mostly borrowed from awt::XWindow interface). + + Since currently (mid 2004), OOo isn't very well suited to + multi-threaded rendering, this class relies on <em>very + frequent</em> external update() calls, which will render the + next frame of animations. This works as follows: after the + displaySlide() has been successfully called (which setup and + starts an actual slide show), the update() method must be + called until it returns false. + Effectively, this puts the burden of providing + concurrency to the clients of this class, which, as noted + above, is currently unavoidable with the current state of + affairs (I've actually tried threading here, but failed + miserably when using the VCL canvas as the render backend - + deadlocked). + + ******************************************************************************/ + +typedef cppu::WeakComponentImplHelper<css::lang::XServiceInfo, presentation::XSlideShow> SlideShowImplBase; + +typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; + +/// Maps XDrawPage for annotations persistence +typedef ::std::map< css::uno::Reference< + css::drawing::XDrawPage>, + PolyPolygonVector> PolygonMap; + +class SlideShowImpl : private cppu::BaseMutex, + public CursorManager, + public MediaFileManager, + public SlideShowImplBase +{ +public: + explicit SlideShowImpl( + uno::Reference<uno::XComponentContext> const& xContext ); + + /** Notify that the transition phase of the current slide + has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the first phase. + + @param bPaintSlide + When true, Slide::show() is passed a true as well, denoting + explicit paint of slide content. Pass false here, if e.g. a + slide transition has already rendered the initial slide image. + */ + void notifySlideTransitionEnded( bool bPaintSlide ); + + /** Notify that the shape animation phase of the current slide + has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the second phase. + */ + void notifySlideAnimationsEnded(); + + /** Notify that the slide has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the third phase. + */ + void notifySlideEnded (const bool bReverse); + + /** Notification from eventmultiplexer that a hyperlink + has been clicked. + */ + bool notifyHyperLinkClicked( OUString const& hyperLink ); + + /** Notification from eventmultiplexer that an animation event has occurred. + This will be forwarded to all registered XSlideShowListener + */ + bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ); + + /** Obtain a MediaTempFile for the specified url. */ + virtual std::shared_ptr<avmedia::MediaTempFile> getMediaTempFile(const OUString& aUrl) override; + +private: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XSlideShow: + virtual sal_Bool SAL_CALL nextEffect() override; + virtual sal_Bool SAL_CALL previousEffect() override; + virtual sal_Bool SAL_CALL startShapeActivity( + uno::Reference<drawing::XShape> const& xShape ) override; + virtual sal_Bool SAL_CALL stopShapeActivity( + uno::Reference<drawing::XShape> const& xShape ) override; + virtual sal_Bool SAL_CALL pause( sal_Bool bPauseShow ) override; + virtual uno::Reference<drawing::XDrawPage> SAL_CALL getCurrentSlide() override; + virtual void SAL_CALL displaySlide( + uno::Reference<drawing::XDrawPage> const& xSlide, + uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, + uno::Reference<animations::XAnimationNode> const& xRootNode, + uno::Sequence<beans::PropertyValue> const& rProperties ) override; + virtual void SAL_CALL registerUserPaintPolygons( const css::uno::Reference< css::lang::XMultiServiceFactory >& xDocFactory ) override; + virtual sal_Bool SAL_CALL setProperty( + beans::PropertyValue const& rProperty ) override; + virtual sal_Bool SAL_CALL addView( + uno::Reference<presentation::XSlideShowView> const& xView ) override; + virtual sal_Bool SAL_CALL removeView( + uno::Reference<presentation::XSlideShowView> const& xView ) override; + virtual sal_Bool SAL_CALL update( double & nNextTimeout ) override; + virtual void SAL_CALL addSlideShowListener( + uno::Reference<presentation::XSlideShowListener> const& xListener ) override; + virtual void SAL_CALL removeSlideShowListener( + uno::Reference<presentation::XSlideShowListener> const& xListener ) override; + virtual void SAL_CALL addShapeEventListener( + uno::Reference<presentation::XShapeEventListener> const& xListener, + uno::Reference<drawing::XShape> const& xShape ) override; + virtual void SAL_CALL removeShapeEventListener( + uno::Reference<presentation::XShapeEventListener> const& xListener, + uno::Reference<drawing::XShape> const& xShape ) override; + virtual void SAL_CALL setShapeCursor( + uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape ) override; + + // CursorManager + + + virtual bool requestCursor( sal_Int16 nCursorShape ) override; + virtual void resetCursor() override; + + /** This is somewhat similar to displaySlide when called for the current + slide. It has been simplified to take advantage of that no slide + change takes place. Furthermore it does not show the slide + transition. + */ + void redisplayCurrentSlide(); + +protected: + // WeakComponentImplHelperBase + virtual void SAL_CALL disposing() override; + + bool isDisposed() const + { + return (rBHelper.bDisposed || rBHelper.bInDispose); + } + +private: + struct SeparateListenerImpl; friend struct SeparateListenerImpl; + class PrefetchPropertiesFunc; friend class PrefetchPropertiesFunc; + + /// Stop currently running show. + void stopShow(); + + ///Find a polygons vector in maPolygons (map) + PolygonMap::iterator findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage); + + /// Creates a new slide. + SlideSharedPtr makeSlide( + uno::Reference<drawing::XDrawPage> const& xDrawPage, + uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, + uno::Reference<animations::XAnimationNode> const& xRootNode ); + + /// Checks whether the given slide/animation node matches mpPrefetchSlide + static bool matches( + SlideSharedPtr const& pSlide, + uno::Reference<drawing::XDrawPage> const& xSlide, + uno::Reference<animations::XAnimationNode> const& xNode ) + { + if (pSlide) + return (pSlide->getXDrawPage() == xSlide && + pSlide->getXAnimationNode() == xNode); + else + return (!xSlide.is() && !xNode.is()); + } + + /// Resets the current slide transition sound object with a new one: + SoundPlayerSharedPtr resetSlideTransitionSound( + uno::Any const& url, bool bLoopSound ); + + /// stops the current slide transition sound + void stopSlideTransitionSound(); + + /** Prepare a slide transition + + This method registers all necessary events and + activities for a slide transition. + + @return the slide change activity, or NULL for no transition effect + */ + ActivitySharedPtr createSlideTransition( + const uno::Reference< drawing::XDrawPage >& xDrawPage, + const SlideSharedPtr& rLeavingSlide, + const SlideSharedPtr& rEnteringSlide, + const EventSharedPtr& rTransitionEndEvent ); + + /** Request/release the wait symbol. The wait symbol is displayed when + there are more requests then releases. Locking the wait symbol + helps to avoid intermediate repaints. + + Do not call this method directly. Use WaitSymbolLock instead. + */ + void requestWaitSymbol(); + void releaseWaitSymbol(); + + class WaitSymbolLock {public: + explicit WaitSymbolLock(SlideShowImpl& rSlideShowImpl) : mrSlideShowImpl(rSlideShowImpl) + { mrSlideShowImpl.requestWaitSymbol(); } + ~WaitSymbolLock() + { mrSlideShowImpl.releaseWaitSymbol(); } + private: SlideShowImpl& mrSlideShowImpl; + }; + + /// Filter requested cursor shape against hard slideshow cursors (wait, etc.) + sal_Int16 calcActiveCursor( sal_Int16 nCursorShape ) const; + + /** This method is called asynchronously to finish the rewinding of an + effect to the previous slide that was initiated earlier. + */ + void rewindEffectToPreviousSlide(); + + /// all registered views + UnoViewContainer maViewContainer; + + /// all registered slide show listeners + comphelper::OInterfaceContainerHelper3<presentation::XSlideShowListener> maListenerContainer; + + /// map of vectors, containing all registered listeners for a shape + ShapeEventListenerMap maShapeEventListeners; + /// map of sal_Int16 values, specifying the mouse cursor for every shape + ShapeCursorMap maShapeCursors; + + //map of vector of Polygons, containing polygons drawn on each slide. + PolygonMap maPolygons; + + std::optional<RGBColor> maUserPaintColor; + + double maUserPaintStrokeWidth; + + //changed for the eraser project + std::optional<bool> maEraseAllInk; + std::optional<sal_Int32> maEraseInk; + //end changed + + std::shared_ptr<canvas::tools::ElapsedTime> mpPresTimer; + ScreenUpdater maScreenUpdater; + EventQueue maEventQueue; + EventMultiplexer maEventMultiplexer; + ActivitiesQueue maActivitiesQueue; + UserEventQueue maUserEventQueue; + SubsettableShapeManagerSharedPtr mpDummyPtr; + box2d::utils::Box2DWorldSharedPtr mpBox2DDummyPtr; + + std::shared_ptr<SeparateListenerImpl> mpListener; + + std::shared_ptr<RehearseTimingsActivity> mpRehearseTimingsActivity; + std::shared_ptr<WaitSymbol> mpWaitSymbol; + + std::shared_ptr<PointerSymbol> mpPointerSymbol; + + /// the current slide transition sound object: + SoundPlayerSharedPtr mpCurrentSlideTransitionSound; + + uno::Reference<uno::XComponentContext> mxComponentContext; + uno::Reference< + presentation::XTransitionFactory> mxOptionalTransitionFactory; + + /// the previously running slide + SlideSharedPtr mpPreviousSlide; + /// the currently running slide + SlideSharedPtr mpCurrentSlide; + /// the already prefetched slide: best candidate for upcoming slide + SlideSharedPtr mpPrefetchSlide; + /// slide to be prefetched: best candidate for upcoming slide + uno::Reference<drawing::XDrawPage> mxPrefetchSlide; + /// save the XDrawPagesSupplier to retrieve polygons + uno::Reference<drawing::XDrawPagesSupplier> mxDrawPagesSupplier; + /// Used by MediaFileManager, for media files with package url. + uno::Reference<document::XStorageBasedDocument> mxSBD; + /// slide animation to be prefetched: + uno::Reference<animations::XAnimationNode> mxPrefetchAnimationNode; + + sal_Int16 mnCurrentCursor; + + sal_Int32 mnWaitSymbolRequestCount; + bool mbAutomaticAdvancementMode; + bool mbImageAnimationsAllowed; + bool mbNoSlideTransitions; + bool mbMouseVisible; + bool mbForceManualAdvance; + bool mbShowPaused; + bool mbSlideShowIdle; + bool mbDisableAnimationZOrder; + + EffectRewinder maEffectRewinder; + FrameSynchronization maFrameSynchronization; +}; + +/** Separate event listener for animation, view and hyperlink events. + + This handler is registered for slide animation end, view and + hyperlink events at the global EventMultiplexer, and forwards + notifications to the SlideShowImpl +*/ +struct SlideShowImpl::SeparateListenerImpl : public EventHandler, + public ViewRepaintHandler, + public HyperlinkHandler, + public AnimationEventHandler +{ + SlideShowImpl& mrShow; + ScreenUpdater& mrScreenUpdater; + EventQueue& mrEventQueue; + + SeparateListenerImpl( SlideShowImpl& rShow, + ScreenUpdater& rScreenUpdater, + EventQueue& rEventQueue ) : + mrShow( rShow ), + mrScreenUpdater( rScreenUpdater ), + mrEventQueue( rEventQueue ) + {} + + SeparateListenerImpl( const SeparateListenerImpl& ) = delete; + SeparateListenerImpl& operator=( const SeparateListenerImpl& ) = delete; + + // EventHandler + virtual bool handleEvent() override + { + // DON't call notifySlideAnimationsEnded() + // directly, but queue an event. handleEvent() + // might be called from e.g. + // showNext(), and notifySlideAnimationsEnded() must not be called + // in recursion. Note that the event is scheduled for the next + // frame so that its expensive execution does not come in between + // sprite hiding and shape redraw (at the end of the animation of a + // shape), which would cause a flicker. + mrEventQueue.addEventForNextRound( + makeEvent( [this] () { this->mrShow.notifySlideAnimationsEnded(); }, + "SlideShowImpl::notifySlideAnimationsEnded")); + return true; + } + + // ViewRepaintHandler + virtual void viewClobbered( const UnoViewSharedPtr& rView ) override + { + // given view needs repaint, request update + mrScreenUpdater.notifyUpdate(rView, true); + } + + // HyperlinkHandler + virtual bool handleHyperlink( OUString const& rLink ) override + { + return mrShow.notifyHyperLinkClicked(rLink); + } + + // AnimationEventHandler + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override + { + return mrShow.handleAnimationEvent(rNode); + } +}; + +SlideShowImpl::SlideShowImpl( + uno::Reference<uno::XComponentContext> const& xContext ) + : SlideShowImplBase(m_aMutex), + maViewContainer(), + maListenerContainer( m_aMutex ), + maShapeEventListeners(), + maShapeCursors(), + maUserPaintColor(), + maUserPaintStrokeWidth(4.0), + mpPresTimer( std::make_shared<canvas::tools::ElapsedTime>() ), + maScreenUpdater(maViewContainer), + maEventQueue( mpPresTimer ), + maEventMultiplexer( maEventQueue, + maViewContainer ), + maActivitiesQueue( mpPresTimer ), + maUserEventQueue( maEventMultiplexer, + maEventQueue, + *this ), + mpDummyPtr(), + mpBox2DDummyPtr(), + mpListener(), + mpRehearseTimingsActivity(), + mpWaitSymbol(), + mpPointerSymbol(), + mpCurrentSlideTransitionSound(), + mxComponentContext( xContext ), + mxOptionalTransitionFactory(), + mpCurrentSlide(), + mpPrefetchSlide(), + mxPrefetchSlide(), + mxDrawPagesSupplier(), + mxSBD(), + mxPrefetchAnimationNode(), + mnCurrentCursor(awt::SystemPointer::ARROW), + mnWaitSymbolRequestCount(0), + mbAutomaticAdvancementMode(false), + mbImageAnimationsAllowed( true ), + mbNoSlideTransitions( false ), + mbMouseVisible( true ), + mbForceManualAdvance( false ), + mbShowPaused( false ), + mbSlideShowIdle( true ), + mbDisableAnimationZOrder( false ), + maEffectRewinder(maEventMultiplexer, maEventQueue, maUserEventQueue), + maFrameSynchronization(1.0 / FrameRate::PreferredFramesPerSecond) + +{ + // keep care not constructing any UNO references to this inside ctor, + // shift that code to create()! + + uno::Reference<lang::XMultiComponentFactory> xFactory( + mxComponentContext->getServiceManager() ); + + if( xFactory.is() ) + { + try + { + // #i82460# try to retrieve special transition factory + mxOptionalTransitionFactory.set( + xFactory->createInstanceWithContext( + "com.sun.star.presentation.TransitionFactory", + mxComponentContext ), + uno::UNO_QUERY ); + } + catch (loader::CannotActivateFactoryException const&) + { + } + } + + mpListener = std::make_shared<SeparateListenerImpl>( + *this, + maScreenUpdater, + maEventQueue ); + maEventMultiplexer.addSlideAnimationsEndHandler( mpListener ); + maEventMultiplexer.addViewRepaintHandler( mpListener ); + maEventMultiplexer.addHyperlinkHandler( mpListener, 0.0 ); + maEventMultiplexer.addAnimationStartHandler( mpListener ); + maEventMultiplexer.addAnimationEndHandler( mpListener ); +} + +// we are about to be disposed (someone call dispose() on us) +void SlideShowImpl::disposing() +{ + osl::MutexGuard const guard( m_aMutex ); + + maEffectRewinder.dispose(); + + // stop slide transition sound, if any: + stopSlideTransitionSound(); + + mxComponentContext.clear(); + + if( mpCurrentSlideTransitionSound ) + { + mpCurrentSlideTransitionSound->dispose(); + mpCurrentSlideTransitionSound.reset(); + } + + mpWaitSymbol.reset(); + mpPointerSymbol.reset(); + + if( mpRehearseTimingsActivity ) + { + mpRehearseTimingsActivity->dispose(); + mpRehearseTimingsActivity.reset(); + } + + if( mpListener ) + { + maEventMultiplexer.removeSlideAnimationsEndHandler(mpListener); + maEventMultiplexer.removeViewRepaintHandler(mpListener); + maEventMultiplexer.removeHyperlinkHandler(mpListener); + maEventMultiplexer.removeAnimationStartHandler( mpListener ); + maEventMultiplexer.removeAnimationEndHandler( mpListener ); + + mpListener.reset(); + } + + maUserEventQueue.clear(); + maActivitiesQueue.clear(); + maEventMultiplexer.clear(); + maEventQueue.clear(); + mpPresTimer.reset(); + maShapeCursors.clear(); + maShapeEventListeners.clear(); + + // send all listeners a disposing() that we are going down: + maListenerContainer.disposeAndClear( + lang::EventObject( static_cast<cppu::OWeakObject *>(this) ) ); + + maViewContainer.dispose(); + + // release slides: + mxPrefetchAnimationNode.clear(); + mxPrefetchSlide.clear(); + mpPrefetchSlide.reset(); + mpCurrentSlide.reset(); + mpPreviousSlide.reset(); +} + +uno::Sequence< OUString > SAL_CALL SlideShowImpl::getSupportedServiceNames() +{ + return { "com.sun.star.presentation.SlideShow" }; +} + +OUString SAL_CALL SlideShowImpl::getImplementationName() +{ + return "com.sun.star.comp.presentation.SlideShow"; +} + +sal_Bool SAL_CALL SlideShowImpl::supportsService(const OUString& aServiceName) +{ + return cppu::supportsService(this, aServiceName); +} + +/// stops the current slide transition sound +void SlideShowImpl::stopSlideTransitionSound() +{ + if (mpCurrentSlideTransitionSound) + { + mpCurrentSlideTransitionSound->stopPlayback(); + mpCurrentSlideTransitionSound->dispose(); + mpCurrentSlideTransitionSound.reset(); + } + } + +SoundPlayerSharedPtr SlideShowImpl::resetSlideTransitionSound( const uno::Any& rSound, bool bLoopSound ) +{ + bool bStopSound = false; + OUString url; + + if( !(rSound >>= bStopSound) ) + bStopSound = false; + rSound >>= url; + + if( !bStopSound && url.isEmpty() ) + return SoundPlayerSharedPtr(); + + stopSlideTransitionSound(); + + if (!url.isEmpty()) + { + try + { + mpCurrentSlideTransitionSound = SoundPlayer::create( + maEventMultiplexer, url, mxComponentContext, *this); + mpCurrentSlideTransitionSound->setPlaybackLoop( bLoopSound ); + } + catch (lang::NoSupportException const&) + { + // catch possible exceptions from SoundPlayer, since + // being not able to playback the sound is not a hard + // error here (still, the slide transition should be + // shown). + } + } + return mpCurrentSlideTransitionSound; +} + +ActivitySharedPtr SlideShowImpl::createSlideTransition( + const uno::Reference< drawing::XDrawPage >& xDrawPage, + const SlideSharedPtr& rLeavingSlide, + const SlideSharedPtr& rEnteringSlide, + const EventSharedPtr& rTransitionEndEvent) +{ + ENSURE_OR_THROW( !maViewContainer.empty(), + "createSlideTransition(): No views" ); + ENSURE_OR_THROW( rEnteringSlide, + "createSlideTransition(): No entering slide" ); + + // return empty transition, if slide transitions + // are disabled. + if (mbNoSlideTransitions) + return ActivitySharedPtr(); + + // retrieve slide change parameters from XDrawPage + uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, + uno::UNO_QUERY ); + + if( !xPropSet.is() ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Slide has no PropertySet - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int16 nTransitionType(0); + if( !getPropertyValue( nTransitionType, + xPropSet, + "TransitionType") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition type from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int16 nTransitionSubType(0); + if( !getPropertyValue( nTransitionSubType, + xPropSet, + "TransitionSubtype") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition subtype from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + bool bTransitionDirection(false); + if( !getPropertyValue( bTransitionDirection, + xPropSet, + "TransitionDirection") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition direction from XDrawPage - assuming default direction" ); + } + + sal_Int32 aUnoColor(0); + if( !getPropertyValue( aUnoColor, + xPropSet, + "TransitionFadeColor") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition fade color from XDrawPage - assuming black" ); + } + + const RGBColor aTransitionFadeColor( unoColor2RGBColor( aUnoColor )); + + uno::Any aSound; + bool bLoopSound = false; + + if( !getPropertyValue( aSound, xPropSet, "Sound") ) + SAL_INFO("slideshow", "createSlideTransition(): Could not determine transition sound effect URL from XDrawPage - using no sound" ); + + if( !getPropertyValue( bLoopSound, xPropSet, "LoopSound" ) ) + SAL_INFO("slideshow", "createSlideTransition(): Could not get slide property 'LoopSound' - using no sound" ); + + NumberAnimationSharedPtr pTransition( + TransitionFactory::createSlideTransition( + rLeavingSlide, + rEnteringSlide, + maViewContainer, + maScreenUpdater, + maEventMultiplexer, + mxOptionalTransitionFactory, + nTransitionType, + nTransitionSubType, + bTransitionDirection, + aTransitionFadeColor, + resetSlideTransitionSound( aSound, bLoopSound ) )); + + if( !pTransition ) + return ActivitySharedPtr(); // no transition effect has been + // generated. Normally, that means + // that simply no transition is + // set on this slide. + + double nTransitionDuration(0.0); + if( !getPropertyValue( nTransitionDuration, + xPropSet, + "TransitionDuration") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition duration from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int32 nMinFrames(5); + if( !getPropertyValue( nMinFrames, + xPropSet, + "MinimalFrameNumber") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "No minimal number of frames given - assuming 5" ); + } + + // prefetch slide transition bitmaps, but postpone it after + // displaySlide() has finished - sometimes, view size has not yet + // reached final size + maEventQueue.addEvent( + makeEvent( [pTransition] () { + pTransition->prefetch(); }, + "Animation::prefetch")); + + return ActivitySharedPtr( + ActivitiesFactory::createSimpleActivity( + ActivitiesFactory::CommonParameters( + rTransitionEndEvent, + maEventQueue, + maActivitiesQueue, + nTransitionDuration, + nMinFrames, + false, + std::optional<double>(1.0), + 0.0, + 0.0, + ShapeSharedPtr(), + basegfx::B2DSize( rEnteringSlide->getSlideSize() ) ), + pTransition, + true )); +} + +PolygonMap::iterator SlideShowImpl::findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage) +{ + // TODO(P2): optimize research in the map. + return maPolygons.find(xDrawPage); +} + +SlideSharedPtr SlideShowImpl::makeSlide( + uno::Reference<drawing::XDrawPage> const& xDrawPage, + uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, + uno::Reference<animations::XAnimationNode> const& xRootNode ) +{ + if( !xDrawPage.is() ) + return SlideSharedPtr(); + + //Retrieve polygons for the current slide + PolygonMap::iterator aIter = findPolygons(xDrawPage); + + const SlideSharedPtr pSlide( createSlide(xDrawPage, + xDrawPages, + xRootNode, + maEventQueue, + maEventMultiplexer, + maScreenUpdater, + maActivitiesQueue, + maUserEventQueue, + *this, + *this, + maViewContainer, + mxComponentContext, + maShapeEventListeners, + maShapeCursors, + (aIter != maPolygons.end()) ? aIter->second : PolyPolygonVector(), + maUserPaintColor ? *maUserPaintColor : RGBColor(), + maUserPaintStrokeWidth, + !!maUserPaintColor, + mbImageAnimationsAllowed, + mbDisableAnimationZOrder) ); + + // prefetch show content (reducing latency for slide + // bitmap and effect start later on) + pSlide->prefetch(); + + return pSlide; +} + +void SlideShowImpl::requestWaitSymbol() +{ + ++mnWaitSymbolRequestCount; + OSL_ASSERT(mnWaitSymbolRequestCount>0); + + if (mnWaitSymbolRequestCount == 1) + { + if( !mpWaitSymbol ) + { + // fall back to cursor + requestCursor(calcActiveCursor(mnCurrentCursor)); + } + else + mpWaitSymbol->show(); + } +} + +void SlideShowImpl::releaseWaitSymbol() +{ + --mnWaitSymbolRequestCount; + OSL_ASSERT(mnWaitSymbolRequestCount>=0); + + if (mnWaitSymbolRequestCount == 0) + { + if( !mpWaitSymbol ) + { + // fall back to cursor + requestCursor(calcActiveCursor(mnCurrentCursor)); + } + else + mpWaitSymbol->hide(); + } +} + +sal_Int16 SlideShowImpl::calcActiveCursor( sal_Int16 nCursorShape ) const +{ + if( mnWaitSymbolRequestCount>0 && !mpWaitSymbol ) // enforce wait cursor + nCursorShape = awt::SystemPointer::WAIT; + else if( !mbMouseVisible ) // enforce INVISIBLE + nCursorShape = awt::SystemPointer::INVISIBLE; + else if( maUserPaintColor && + nCursorShape == awt::SystemPointer::ARROW ) + nCursorShape = awt::SystemPointer::PEN; + + return nCursorShape; +} + +void SlideShowImpl::stopShow() +{ + // Force-end running animation + // =========================== + if (mpCurrentSlide) + { + mpCurrentSlide->hide(); + //Register polygons in the map + if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) + maPolygons.erase(mpCurrentSlide->getXDrawPage()); + + maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); + } + + // clear all queues + maEventQueue.clear(); + maActivitiesQueue.clear(); + + // Attention: we MUST clear the user event queue here, + // this is because the current slide might have registered + // shape events (click or enter/leave), which might + // otherwise dangle forever in the queue (because of the + // shared ptr nature). If someone needs to change this: + // somehow unregister those shapes at the user event queue + // on notifySlideEnded(). + maUserEventQueue.clear(); + + // re-enable automatic effect advancement + // (maEventQueue.clear() above might have killed + // maEventMultiplexer's tick events) + if (mbAutomaticAdvancementMode) + { + // toggle automatic mode (enabling just again is + // ignored by EventMultiplexer) + maEventMultiplexer.setAutomaticMode( false ); + maEventMultiplexer.setAutomaticMode( true ); + } +} + +class SlideShowImpl::PrefetchPropertiesFunc +{ +public: + PrefetchPropertiesFunc( SlideShowImpl * that_, + bool& rbSkipAllMainSequenceEffects, + bool& rbSkipSlideTransition) + : mpSlideShowImpl(that_), + mrbSkipAllMainSequenceEffects(rbSkipAllMainSequenceEffects), + mrbSkipSlideTransition(rbSkipSlideTransition) + {} + + void operator()( beans::PropertyValue const& rProperty ) const { + if (rProperty.Name == "Prefetch" ) + { + uno::Sequence<uno::Any> seq; + if ((rProperty.Value >>= seq) && seq.getLength() == 2) + { + seq[0] >>= mpSlideShowImpl->mxPrefetchSlide; + seq[1] >>= mpSlideShowImpl->mxPrefetchAnimationNode; + } + } + else if ( rProperty.Name == "SkipAllMainSequenceEffects" ) + { + rProperty.Value >>= mrbSkipAllMainSequenceEffects; + } + else if ( rProperty.Name == "SkipSlideTransition" ) + { + rProperty.Value >>= mrbSkipSlideTransition; + } + else + { + SAL_WARN( "slideshow", rProperty.Name ); + } + } +private: + SlideShowImpl *const mpSlideShowImpl; + bool& mrbSkipAllMainSequenceEffects; + bool& mrbSkipSlideTransition; +}; + +void SlideShowImpl::displaySlide( + uno::Reference<drawing::XDrawPage> const& xSlide, + uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, + uno::Reference<animations::XAnimationNode> const& xRootNode, + uno::Sequence<beans::PropertyValue> const& rProperties ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + maEffectRewinder.setRootAnimationNode(xRootNode); + maEffectRewinder.setCurrentSlide(xSlide); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + mxDrawPagesSupplier = xDrawPages; + mxSBD = uno::Reference<document::XStorageBasedDocument>(mxDrawPagesSupplier, uno::UNO_QUERY); + + stopShow(); // MUST call that: results in + // maUserEventQueue.clear(). What's more, + // stopShow()'s currSlide->hide() call is + // now also required, notifySlideEnded() + // relies on that + // unconditionally. Otherwise, genuine + // shape animations (drawing layer and + // GIF) will not be stopped. + + bool bSkipAllMainSequenceEffects (false); + bool bSkipSlideTransition (false); + std::for_each( rProperties.begin(), + rProperties.end(), + PrefetchPropertiesFunc(this, bSkipAllMainSequenceEffects, bSkipSlideTransition) ); + + OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); + if (maViewContainer.empty()) + return; + + // this here might take some time + { + WaitSymbolLock aLock (*this); + + mpPreviousSlide = mpCurrentSlide; + mpCurrentSlide.reset(); + + if (matches( mpPrefetchSlide, xSlide, xRootNode )) + { + // prefetched slide matches: + mpCurrentSlide = mpPrefetchSlide; + } + else + mpCurrentSlide = makeSlide( xSlide, xDrawPages, xRootNode ); + + OSL_ASSERT( mpCurrentSlide ); + if (mpCurrentSlide) + { + basegfx::B2DSize oldSlideSize; + if( mpPreviousSlide ) + oldSlideSize = basegfx::B2DSize( mpPreviousSlide->getSlideSize() ); + + basegfx::B2DSize const slideSize( mpCurrentSlide->getSlideSize() ); + + // push new transformation to all views, if size changed + if( !mpPreviousSlide || oldSlideSize != slideSize ) + { + for( const auto& pView : maViewContainer ) + pView->setViewSize( slideSize ); + + // explicitly notify view change here, + // because transformation might have changed: + // optimization, this->notifyViewChange() would + // repaint slide which is not necessary. + maEventMultiplexer.notifyViewsChanged(); + } + + // create slide transition, and add proper end event + // (which then starts the slide effects + // via CURRENT_SLIDE.show()) + ActivitySharedPtr pSlideChangeActivity ( + createSlideTransition( + mpCurrentSlide->getXDrawPage(), + mpPreviousSlide, + mpCurrentSlide, + makeEvent( + [this] () { this->notifySlideTransitionEnded(false); }, + "SlideShowImpl::notifySlideTransitionEnded"))); + + if (bSkipSlideTransition) + { + // The transition activity was created for the side effects + // (like sound transitions). Because we want to skip the + // actual transition animation we do not need the activity + // anymore. + pSlideChangeActivity.reset(); + } + + if (pSlideChangeActivity) + { + // factory generated a slide transition - activate it! + maActivitiesQueue.addActivity( pSlideChangeActivity ); + } + else + { + // no transition effect on this slide - schedule slide + // effect start event right away. + maEventQueue.addEvent( + makeEvent( + [this] () { this->notifySlideTransitionEnded(true); }, + "SlideShowImpl::notifySlideTransitionEnded")); + } + } + } // finally + + maListenerContainer.forEach( + [](uno::Reference<presentation::XSlideShowListener> const& xListener) + { + xListener->slideTransitionStarted(); + }); + + // We are currently rewinding an effect. This lead us from the next + // slide to this one. To complete this we have to play back all main + // sequence effects on this slide. + if (bSkipAllMainSequenceEffects) + maEffectRewinder.skipAllMainSequenceEffects(); +} + +void SlideShowImpl::redisplayCurrentSlide() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + stopShow(); + + OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); + if (maViewContainer.empty()) + return; + + // No transition effect on this slide - schedule slide + // effect start event right away. + maEventQueue.addEvent( + makeEvent( [this] () { this->notifySlideTransitionEnded(true); }, + "SlideShowImpl::notifySlideTransitionEnded")); + + maListenerContainer.forEach( + [](uno::Reference<presentation::XSlideShowListener> const& xListener) + { + xListener->slideTransitionStarted(); + }); +} + +sal_Bool SlideShowImpl::nextEffect() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mbShowPaused) + return true; + else + return maEventMultiplexer.notifyNextEffect(); +} + +sal_Bool SlideShowImpl::previousEffect() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mbShowPaused) + return true; + else + { + return maEffectRewinder.rewind( + maScreenUpdater.createLock(), + [this]() { return this->redisplayCurrentSlide(); }, + [this]() { return this->rewindEffectToPreviousSlide(); } ); + } +} + +void SlideShowImpl::rewindEffectToPreviousSlide() +{ + // Show the wait symbol now and prevent it from showing temporary slide + // content while effects are played back. + WaitSymbolLock aLock (*this); + + // A previous call to EffectRewinder::Rewind could not rewind the current + // effect because there are no effects on the current slide or none has + // yet been displayed. Go to the previous slide. + notifySlideEnded(true); + + // Process pending events once more in order to have the following + // screen update show the last effect. Not sure whether this should be + // necessary. + maEventQueue.forceEmpty(); + + // We have to call the screen updater before the wait symbol is turned + // off. Otherwise the wait symbol would force the display of an + // intermediate state of the slide (before the effects are replayed.) + maScreenUpdater.commitUpdates(); +} + +sal_Bool SlideShowImpl::startShapeActivity( + uno::Reference<drawing::XShape> const& /*xShape*/ ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // TODO(F3): NYI + OSL_FAIL( "not yet implemented!" ); + return false; +} + +sal_Bool SlideShowImpl::stopShapeActivity( + uno::Reference<drawing::XShape> const& /*xShape*/ ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // TODO(F3): NYI + OSL_FAIL( "not yet implemented!" ); + return false; +} + +sal_Bool SlideShowImpl::pause( sal_Bool bPauseShow ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (bPauseShow) + mpPresTimer->pauseTimer(); + else + mpPresTimer->continueTimer(); + + maEventMultiplexer.notifyPauseMode(bPauseShow); + + mbShowPaused = bPauseShow; + return true; +} + +uno::Reference<drawing::XDrawPage> SlideShowImpl::getCurrentSlide() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return uno::Reference<drawing::XDrawPage>(); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mpCurrentSlide) + return mpCurrentSlide->getXDrawPage(); + else + return uno::Reference<drawing::XDrawPage>(); +} + +sal_Bool SlideShowImpl::addView( + uno::Reference<presentation::XSlideShowView> const& xView ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // first of all, check if view has a valid canvas + ENSURE_OR_RETURN_FALSE( xView.is(), "addView(): Invalid view" ); + ENSURE_OR_RETURN_FALSE( xView->getCanvas().is(), + "addView(): View does not provide a valid canvas" ); + + UnoViewSharedPtr const pView( createSlideView( + xView, + maEventQueue, + maEventMultiplexer )); + if (!maViewContainer.addView( pView )) + return false; // view already added + + // initialize view content + // ======================= + + if (mpCurrentSlide) + { + // set view transformation + const basegfx::B2ISize slideSize = mpCurrentSlide->getSlideSize(); + pView->setViewSize( basegfx::B2DSize( slideSize.getX(), + slideSize.getY() ) ); + } + + // clear view area (since it's newly added, + // we need a clean slate) + pView->clearAll(); + + // broadcast newly added view + maEventMultiplexer.notifyViewAdded( pView ); + + // set current mouse ptr + pView->setCursorShape( calcActiveCursor(mnCurrentCursor) ); + + return true; +} + +sal_Bool SlideShowImpl::removeView( + uno::Reference<presentation::XSlideShowView> const& xView ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ENSURE_OR_RETURN_FALSE( xView.is(), "removeView(): Invalid view" ); + + UnoViewSharedPtr const pView( maViewContainer.removeView( xView ) ); + if( !pView ) + return false; // view was not added in the first place + + // remove view from EventMultiplexer (mouse events etc.) + maEventMultiplexer.notifyViewRemoved( pView ); + + pView->_dispose(); + + return true; +} + +void SlideShowImpl::registerUserPaintPolygons( const uno::Reference< lang::XMultiServiceFactory >& xDocFactory ) +{ + //Retrieve Polygons if user ends presentation by context menu + if (mpCurrentSlide) + { + if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) + maPolygons.erase(mpCurrentSlide->getXDrawPage()); + + maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); + } + + //Creating the layer for shapes drawn during slideshow + // query for the XLayerManager + uno::Reference< drawing::XLayerSupplier > xLayerSupplier(xDocFactory, uno::UNO_QUERY); + uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager(); + uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY); + + // create layer + uno::Reference< drawing::XLayer > xDrawnInSlideshow; + uno::Any aPropLayer; + OUString sLayerName = "DrawnInSlideshow"; + if (xNameAccess->hasByName(sLayerName)) + { + xNameAccess->getByName(sLayerName) >>= xDrawnInSlideshow; + } + else + { + xDrawnInSlideshow = xLayerManager->insertNewByIndex(xLayerManager->getCount()); + aPropLayer <<= sLayerName; + xDrawnInSlideshow->setPropertyValue("Name", aPropLayer); + } + + // ODF defaults from ctor of SdrLayer are not automatically set on the here + // created XLayer. Need to be done explicitly here. + aPropLayer <<= true; + xDrawnInSlideshow->setPropertyValue("IsVisible", aPropLayer); + xDrawnInSlideshow->setPropertyValue("IsPrintable", aPropLayer); + aPropLayer <<= false; + xDrawnInSlideshow->setPropertyValue("IsLocked", aPropLayer); + + //Register polygons for each slide + for( const auto& rPoly : maPolygons ) + { + PolyPolygonVector aPolygons = rPoly.second; + //Get shapes for the slide + css::uno::Reference< css::drawing::XShapes > Shapes = rPoly.first; + //Retrieve polygons for one slide + for( const auto& pPolyPoly : aPolygons ) + { + ::basegfx::B2DPolyPolygon b2DPolyPoly = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(pPolyPoly->getUNOPolyPolygon()); + + //Normally there is only one polygon + for(sal_uInt32 i=0; i< b2DPolyPoly.count();i++) + { + const ::basegfx::B2DPolygon& aPoly = b2DPolyPoly.getB2DPolygon(i); + sal_uInt32 nPoints = aPoly.count(); + + if( nPoints > 1) + { + //create the PolyLineShape + uno::Reference< uno::XInterface > polyshape(xDocFactory->createInstance( + "com.sun.star.drawing.PolyLineShape" ) ); + uno::Reference< drawing::XShape > rPolyShape(polyshape, uno::UNO_QUERY); + + //Add the shape to the slide + Shapes->add(rPolyShape); + + //Retrieve shape properties + uno::Reference< beans::XPropertySet > aXPropSet( rPolyShape, uno::UNO_QUERY ); + //Construct a sequence of points sequence + drawing::PointSequenceSequence aRetval; + //Create only one sequence for one polygon + aRetval.realloc( 1 ); + // Retrieve the sequence of points from aRetval + drawing::PointSequence* pOuterSequence = aRetval.getArray(); + // Create 2 points in this sequence + pOuterSequence->realloc(nPoints); + // Get these points which are in an array + awt::Point* pInnerSequence = pOuterSequence->getArray(); + for( sal_uInt32 n = 0; n < nPoints; n++ ) + { + //Create a point from the polygon + *pInnerSequence++ = awt::Point( + basegfx::fround(aPoly.getB2DPoint(n).getX()), + basegfx::fround(aPoly.getB2DPoint(n).getY())); + } + + //Fill the properties + //Give the built PointSequenceSequence. + uno::Any aParam; + aParam <<= aRetval; + aXPropSet->setPropertyValue("PolyPolygon", aParam ); + + //LineStyle : SOLID by default + drawing::LineStyle eLS; + eLS = drawing::LineStyle_SOLID; + aXPropSet->setPropertyValue("LineStyle", uno::Any(eLS) ); + + //LineColor + sal_uInt32 nLineColor; + nLineColor = pPolyPoly->getRGBALineColor(); + //Transform polygon color from RRGGBBAA to AARRGGBB + aXPropSet->setPropertyValue("LineColor", uno::Any(RGBAColor2UnoColor(nLineColor)) ); + + //LineWidth + double fLineWidth; + fLineWidth = pPolyPoly->getStrokeWidth(); + aXPropSet->setPropertyValue("LineWidth", uno::Any(static_cast<sal_Int32>(fLineWidth)) ); + + // make polygons special + xLayerManager->attachShapeToLayer(rPolyShape, xDrawnInSlideshow); + } + } + } + } +} + +sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if ( rProperty.Name == "AutomaticAdvancement" ) + { + double nTimeout(0.0); + mbAutomaticAdvancementMode = (rProperty.Value >>= nTimeout); + if (mbAutomaticAdvancementMode) + { + maEventMultiplexer.setAutomaticTimeout( nTimeout ); + } + maEventMultiplexer.setAutomaticMode( mbAutomaticAdvancementMode ); + return true; + } + + if ( rProperty.Name == "UserPaintColor" ) + { + sal_Int32 nColor(0); + if (rProperty.Value >>= nColor) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maUserPaintColor = unoColor2RGBColor(nColor); + if( mpCurrentSlide && !mpCurrentSlide->isPaintOverlayActive() ) + mpCurrentSlide->enablePaintOverlay(); + + maEventMultiplexer.notifyUserPaintColor( *maUserPaintColor ); + } + else + { + // disable user paint + maUserPaintColor.reset(); + maEventMultiplexer.notifyUserPaintDisabled(); + } + + resetCursor(); + + return true; + } + + //adding support for erasing features in UserPaintOverlay + if ( rProperty.Name == "EraseAllInk" ) + { + bool bEraseAllInk(false); + if (rProperty.Value >>= bEraseAllInk) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maEraseAllInk = bEraseAllInk; + maEventMultiplexer.notifyEraseAllInk( *maEraseAllInk ); + } + + return true; + } + + if ( rProperty.Name == "SwitchPenMode" ) + { + bool bSwitchPenMode(false); + if (rProperty.Value >>= bSwitchPenMode) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + if(bSwitchPenMode){ + // Switch to Pen Mode + maEventMultiplexer.notifySwitchPenMode(); + } + } + return true; + } + + if ( rProperty.Name == "SwitchEraserMode" ) + { + bool bSwitchEraserMode(false); + if (rProperty.Value >>= bSwitchEraserMode) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + if(bSwitchEraserMode){ + // switch to Eraser mode + maEventMultiplexer.notifySwitchEraserMode(); + } + } + + return true; + } + + if ( rProperty.Name == "EraseInk" ) + { + sal_Int32 nEraseInk(100); + if (rProperty.Value >>= nEraseInk) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maEraseInk = nEraseInk; + maEventMultiplexer.notifyEraseInkWidth( *maEraseInk ); + } + + return true; + } + + // new Property for pen's width + if ( rProperty.Name == "UserPaintStrokeWidth" ) + { + double nWidth(4.0); + if (rProperty.Value >>= nWidth) + { + OSL_ENSURE( mbMouseVisible,"setProperty(): User paint overrides invisible mouse" ); + // enable user paint stroke width + maUserPaintStrokeWidth = nWidth; + maEventMultiplexer.notifyUserPaintStrokeWidth( maUserPaintStrokeWidth ); + } + + return true; + } + + if ( rProperty.Name == "AdvanceOnClick" ) + { + bool bAdvanceOnClick = false; + if (! (rProperty.Value >>= bAdvanceOnClick)) + return false; + maUserEventQueue.setAdvanceOnClick( bAdvanceOnClick ); + return true; + } + + if ( rProperty.Name == "DisableAnimationZOrder" ) + { + bool bDisableAnimationZOrder = false; + if (! (rProperty.Value >>= bDisableAnimationZOrder)) + return false; + mbDisableAnimationZOrder = bDisableAnimationZOrder; + return true; + } + + if ( rProperty.Name == "ImageAnimationsAllowed" ) + { + if (! (rProperty.Value >>= mbImageAnimationsAllowed)) + return false; + + // TODO(F3): Forward to slides! + return true; + } + + if ( rProperty.Name == "MouseVisible" ) + { + if (! (rProperty.Value >>= mbMouseVisible)) + return false; + + requestCursor(mnCurrentCursor); + + return true; + } + + if ( rProperty.Name == "ForceManualAdvance" ) + { + return (rProperty.Value >>= mbForceManualAdvance); + } + + if ( rProperty.Name == "RehearseTimings" ) + { + bool bRehearseTimings = false; + if (! (rProperty.Value >>= bRehearseTimings)) + return false; + + if (bRehearseTimings) + { + // TODO(Q3): Move to slide + mpRehearseTimingsActivity = RehearseTimingsActivity::create( + SlideShowContext( + mpDummyPtr, + maEventQueue, + maEventMultiplexer, + maScreenUpdater, + maActivitiesQueue, + maUserEventQueue, + *this, + *this, + maViewContainer, + mxComponentContext, + mpBox2DDummyPtr ) ); + } + else if (mpRehearseTimingsActivity) + { + // removes timer from all views: + mpRehearseTimingsActivity->dispose(); + mpRehearseTimingsActivity.reset(); + } + return true; + } + + if ( rProperty.Name == "WaitSymbolBitmap" ) + { + uno::Reference<rendering::XBitmap> xBitmap; + if (! (rProperty.Value >>= xBitmap)) + return false; + + mpWaitSymbol = WaitSymbol::create( xBitmap, + maScreenUpdater, + maEventMultiplexer, + maViewContainer ); + + return true; + } + + if ( rProperty.Name == "PointerSymbolBitmap" ) + { + uno::Reference<rendering::XBitmap> xBitmap; + if (! (rProperty.Value >>= xBitmap)) + return false; + + mpPointerSymbol = PointerSymbol::create( xBitmap, + maScreenUpdater, + maEventMultiplexer, + maViewContainer ); + + return true; + } + + if ( rProperty.Name == "PointerVisible" ) + { + bool visible; + if (!(rProperty.Value >>= visible)) + return false; + + mpPointerSymbol->setVisible(visible); + return true; + } + + if ( rProperty.Name == "PointerPosition") + { + css::geometry::RealPoint2D pos; + if (! (rProperty.Value >>= pos)) + return false; + + mpPointerSymbol->viewsChanged(pos); + return true; + } + + if (rProperty.Name == "NoSlideTransitions" ) + { + return (rProperty.Value >>= mbNoSlideTransitions); + } + + if ( rProperty.Name == "IsSoundEnabled" ) + { + uno::Sequence<uno::Any> aValues; + uno::Reference<presentation::XSlideShowView> xView; + bool bValue (false); + if ((rProperty.Value >>= aValues) + && aValues.getLength()==2 + && (aValues[0] >>= xView) + && (aValues[1] >>= bValue)) + { + // Look up the view. + auto iView = std::find_if(maViewContainer.begin(), maViewContainer.end(), + [&xView](const UnoViewSharedPtr& rxView) { return rxView && rxView->getUnoView() == xView; }); + if (iView != maViewContainer.end()) + { + // Store the flag at the view so that media shapes have + // access to it. + (*iView)->setIsSoundEnabled(bValue); + return true; + } + } + } + + return false; +} + +void SlideShowImpl::addSlideShowListener( + uno::Reference<presentation::XSlideShowListener> const& xListener ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // container syncs with passed mutex ref + maListenerContainer.addInterface(xListener); +} + +void SlideShowImpl::removeSlideShowListener( + uno::Reference<presentation::XSlideShowListener> const& xListener ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // container syncs with passed mutex ref + maListenerContainer.removeInterface(xListener); +} + +void SlideShowImpl::addShapeEventListener( + uno::Reference<presentation::XShapeEventListener> const& xListener, + uno::Reference<drawing::XShape> const& xShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeEventListenerMap::iterator aIter; + if( (aIter=maShapeEventListeners.find( xShape )) == + maShapeEventListeners.end() ) + { + // no entry for this shape -> create one + aIter = maShapeEventListeners.emplace( + xShape, + std::make_shared<comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener>>( + m_aMutex)).first; + } + + // add new listener to broadcaster + if( aIter->second ) + aIter->second->addInterface( xListener ); + + maEventMultiplexer.notifyShapeListenerAdded(xShape); +} + +void SlideShowImpl::removeShapeEventListener( + uno::Reference<presentation::XShapeEventListener> const& xListener, + uno::Reference<drawing::XShape> const& xShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeEventListenerMap::iterator aIter; + if( (aIter = maShapeEventListeners.find( xShape )) != + maShapeEventListeners.end() ) + { + // entry for this shape found -> remove listener from + // helper object + ENSURE_OR_THROW( + aIter->second, + "SlideShowImpl::removeShapeEventListener(): " + "listener map contains NULL broadcast helper" ); + + aIter->second->removeInterface( xListener ); + } + + maEventMultiplexer.notifyShapeListenerRemoved(xShape); +} + +void SlideShowImpl::setShapeCursor( + uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeCursorMap::iterator aIter; + if( (aIter=maShapeCursors.find( xShape )) == maShapeCursors.end() ) + { + // no entry for this shape -> create one + if( nPointerShape != awt::SystemPointer::ARROW ) + { + // add new entry, unless shape shall display + // normal pointer arrow -> no need to handle that + // case + maShapeCursors.emplace(xShape, nPointerShape); + } + } + else if( nPointerShape == awt::SystemPointer::ARROW ) + { + // shape shall display normal cursor -> can disable + // the cursor and clear the entry + maShapeCursors.erase( xShape ); + } + else + { + // existing entry found, update with new cursor ID + aIter->second = nPointerShape; + } +} + +bool SlideShowImpl::requestCursor( sal_Int16 nCursorShape ) +{ + mnCurrentCursor = nCursorShape; + + const sal_Int16 nActualCursor = calcActiveCursor(mnCurrentCursor); + + // change all views to the requested cursor ID + for( const auto& pView : maViewContainer ) + pView->setCursorShape( nActualCursor ); + + return nActualCursor==nCursorShape; +} + +void SlideShowImpl::resetCursor() +{ + mnCurrentCursor = awt::SystemPointer::ARROW; + + const sal_Int16 nActualCursor = calcActiveCursor( mnCurrentCursor ); + // change all views to the default cursor ID + for( const auto& pView : maViewContainer ) + pView->setCursorShape( nActualCursor ); +} + +sal_Bool SlideShowImpl::update( double & nNextTimeout ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: update() must only be called from the + // main thread! + DBG_TESTSOLARMUTEX(); + + if( mbShowPaused ) + { + // commit frame (might be repaints pending) + maScreenUpdater.commitUpdates(); + + return false; + } + else + { + // TODO(F2): re-evaluate whether that timer lagging makes + // sense. + + // hold timer, while processing the queues: + // 1. when there is more than one active activity this ensures the + // same time for all activities and events + // 2. processing of events may lead to creation of further events + // that have zero delay. While the timer is stopped these events + // are processed in the same run. + { + //Get a shared-ptr that outlives the scope-guard which will + //ensure that the pointed-to-item exists in the case of a + //::dispose clearing mpPresTimer + std::shared_ptr<canvas::tools::ElapsedTime> xTimer(mpPresTimer); + comphelper::ScopeGuard scopeGuard( + [&xTimer]() { return xTimer->releaseTimer(); } ); + xTimer->holdTimer(); + + // process queues + maEventQueue.process(); + + // #i118671# the call above may execute a macro bound to an object. In + // that case this macro may have destroyed this local slideshow so that it + // is disposed (see bugdoc at task). In that case, detect this and exit + // gently from this slideshow. Do not forget to disable the scoped + // call to mpPresTimer, this will be deleted if we are disposed. + if (isDisposed()) + { + scopeGuard.dismiss(); + return false; + } + + maActivitiesQueue.process(); + + // commit frame to screen + maFrameSynchronization.Synchronize(); + maScreenUpdater.commitUpdates(); + + // TODO(Q3): remove need to call dequeued() from + // activities. feels like a wart. + + // Rationale for ActivitiesQueue::processDequeued(): when + // an activity ends, it usually pushed the end state to + // the animated shape in question, and ends the animation + // (which, in turn, will usually disable shape sprite + // mode). Disabling shape sprite mode causes shape + // repaint, which, depending on slide content, takes + // considerably more time than sprite updates. Thus, the + // last animation step tends to look delayed. To + // camouflage this, reaching end position and disabling + // sprite mode is split into two (normal Activity::end(), + // and Activity::dequeued()). Now, the reason to call + // commitUpdates() twice here is caused by the unrelated + // fact that during wait cursor display/hide, the screen + // is updated, and shows hidden sprites, but, in case of + // leaving the second commitUpdates() call out and punting + // that to the next round, no updated static slide + // content. In short, the last shape animation of a slide + // tends to blink at its end. + + // process dequeued activities _after_ commit to screen + maActivitiesQueue.processDequeued(); + + // commit frame to screen + maScreenUpdater.commitUpdates(); + } + // Time held until here + + const bool bActivitiesLeft = ! maActivitiesQueue.isEmpty(); + const bool bTimerEventsLeft = ! maEventQueue.isEmpty(); + const bool bRet = (bActivitiesLeft || bTimerEventsLeft); + + if (bRet) + { + // calc nNextTimeout value: + if (bActivitiesLeft) + { + // Activity queue is not empty. Tell caller that we would + // like to render another frame. + + // Return a zero time-out to signal our caller to call us + // back as soon as possible. The actual timing, waiting the + // appropriate amount of time between frames, is then done + // by the maFrameSynchronization object. + nNextTimeout = 0; + maFrameSynchronization.Activate(); + } + else + { + // timer events left: + // difference from current time (nota bene: + // time no longer held here!) to the next event in + // the event queue. + + // #i61190# Retrieve next timeout only _after_ + // processing activity queue + + // ensure positive value: + nNextTimeout = std::max( 0.0, maEventQueue.nextTimeout() ); + + // There is no active animation so the frame rate does not + // need to be synchronized. + maFrameSynchronization.Deactivate(); + } + + mbSlideShowIdle = false; + } + +#if defined(DBG_UTIL) + // when slideshow is idle, issue an XUpdatable::update() call + // exactly once after a previous animation sequence finished - + // this might trigger screen dumps on some canvas + // implementations + if( !mbSlideShowIdle && + (!bRet || + nNextTimeout > 1.0) ) + { + for( const auto& pView : maViewContainer ) + { + try + { + uno::Reference< presentation::XSlideShowView > xView( pView->getUnoView(), + uno::UNO_SET_THROW ); + uno::Reference<util::XUpdatable> const xUpdatable( + xView->getCanvas(), uno::UNO_QUERY); + if (xUpdatable.is()) // not supported in PresenterCanvas + { + xUpdatable->update(); + } + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + + mbSlideShowIdle = true; + } +#endif + + return bRet; + } +} + +void SlideShowImpl::notifySlideTransitionEnded( bool bPaintSlide ) +{ + osl::MutexGuard const guard( m_aMutex ); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + OSL_ENSURE( mpCurrentSlide, + "notifySlideTransitionEnded(): Invalid current slide" ); + if (mpCurrentSlide) + { + mpCurrentSlide->update_settings( !!maUserPaintColor, maUserPaintColor ? *maUserPaintColor : RGBColor(), maUserPaintStrokeWidth ); + + // first init show, to give the animations + // the chance to register SlideStartEvents + const bool bBackgroundLayerRendered( !bPaintSlide ); + mpCurrentSlide->show( bBackgroundLayerRendered ); + maEventMultiplexer.notifySlideStartEvent(); + } +} + +void queryAutomaticSlideTransition( uno::Reference<drawing::XDrawPage> const& xDrawPage, + double& nAutomaticNextSlideTimeout, + bool& bHasAutomaticNextSlide ) +{ + // retrieve slide change parameters from XDrawPage + // =============================================== + + uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, + uno::UNO_QUERY ); + + sal_Int32 nChange(0); + if( !xPropSet.is() || + !getPropertyValue( nChange, + xPropSet, + "Change") ) + { + SAL_INFO("slideshow", + "queryAutomaticSlideTransition(): " + "Could not extract slide change mode from XDrawPage - assuming <none>" ); + } + + bHasAutomaticNextSlide = nChange == 1; + + if( !xPropSet.is() || + !getPropertyValue( nAutomaticNextSlideTimeout, + xPropSet, + "HighResDuration") ) + { + SAL_INFO("slideshow", + "queryAutomaticSlideTransition(): " + "Could not extract slide transition timeout from " + "XDrawPage - assuming 1 sec" ); + } +} + +void SlideShowImpl::notifySlideAnimationsEnded() +{ + osl::MutexGuard const guard( m_aMutex ); + + //Draw polygons above animations + mpCurrentSlide->drawPolygons(); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + + // This struct will receive the (interruptable) event, + // that triggers the notifySlideEnded() method. + InterruptableEventPair aNotificationEvents; + + if( maEventMultiplexer.getAutomaticMode() ) + { + OSL_ENSURE( ! mpRehearseTimingsActivity, + "unexpected: RehearseTimings mode!" ); + + // schedule a slide end event, with automatic mode's + // delay + aNotificationEvents = makeInterruptableDelay( + [this]() { return this->notifySlideEnded( false ); }, + maEventMultiplexer.getAutomaticTimeout() ); + } + else + { + OSL_ENSURE( mpCurrentSlide, + "notifySlideAnimationsEnded(): Invalid current slide!" ); + + bool bHasAutomaticNextSlide=false; + double nAutomaticNextSlideTimeout=0.0; + queryAutomaticSlideTransition(mpCurrentSlide->getXDrawPage(), + nAutomaticNextSlideTimeout, + bHasAutomaticNextSlide); + + // check whether slide transition should happen + // 'automatically'. If yes, simply schedule the + // specified timeout. + // NOTE: mbForceManualAdvance and mpRehearseTimingsActivity + // override any individual slide setting, to always + // step slides manually. + if( !mbForceManualAdvance && + !mpRehearseTimingsActivity && + bHasAutomaticNextSlide ) + { + aNotificationEvents = makeInterruptableDelay( + [this]() { return this->notifySlideEnded( false ); }, + nAutomaticNextSlideTimeout); + + // TODO(F2): Provide a mechanism to let the user override + // this automatic timeout via next() + } + else + { + if (mpRehearseTimingsActivity) + mpRehearseTimingsActivity->start(); + + // generate click event. Thus, the user must + // trigger the actual end of a slide. No need to + // generate interruptable event here, there's no + // timeout involved. + aNotificationEvents.mpImmediateEvent = + makeEvent( [this] () { this->notifySlideEnded(false); }, + "SlideShowImpl::notifySlideEnded"); + } + } + + // register events on the queues. To make automatic slide + // changes interruptable, register the interruption event + // as a nextEffectEvent target. Note that the timeout + // event is optional (e.g. manual slide changes don't + // generate a timeout) + maUserEventQueue.registerNextEffectEvent( + aNotificationEvents.mpImmediateEvent ); + + if( aNotificationEvents.mpTimeoutEvent ) + maEventQueue.addEvent( aNotificationEvents.mpTimeoutEvent ); + + // current slide's main sequence is over. Now should be + // the time to prefetch the next slide (if any), and + // prepare the initial slide bitmap (speeds up slide + // change setup time a lot). Show the wait cursor, this + // indeed might take some seconds. + { + WaitSymbolLock aLock (*this); + + if (! matches( mpPrefetchSlide, + mxPrefetchSlide, mxPrefetchAnimationNode )) + { + mpPrefetchSlide = makeSlide( mxPrefetchSlide, mxDrawPagesSupplier, + mxPrefetchAnimationNode ); + } + if (mpPrefetchSlide) + { + // ignore return value, this is just to populate + // Slide's internal bitmap buffer, such that the time + // needed to generate the slide bitmap is not spent + // when the slide change is requested. + mpPrefetchSlide->getCurrentSlideBitmap( *maViewContainer.begin() ); + } + } // finally + + maListenerContainer.forEach( + [](uno::Reference<presentation::XSlideShowListener> const& xListener) + { + xListener->slideAnimationsEnded(); + }); +} + +void SlideShowImpl::notifySlideEnded (const bool bReverse) +{ + osl::MutexGuard const guard( m_aMutex ); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + + if (mpRehearseTimingsActivity && !bReverse) + { + const double time = mpRehearseTimingsActivity->stop(); + if (mpRehearseTimingsActivity->hasBeenClicked()) + { + // save time at current drawpage: + uno::Reference<beans::XPropertySet> xPropSet( + mpCurrentSlide->getXDrawPage(), uno::UNO_QUERY ); + OSL_ASSERT( xPropSet.is() ); + if (xPropSet.is()) + { + xPropSet->setPropertyValue( + "Change", + uno::Any( static_cast<sal_Int32>(1) ) ); + xPropSet->setPropertyValue( + "Duration", + uno::Any( static_cast<sal_Int32>(time) ) ); + } + } + } + + if (bReverse) + maEventMultiplexer.notifySlideEndEvent(); + + stopShow(); // MUST call that: results in + // maUserEventQueue.clear(). What's more, + // stopShow()'s currSlide->hide() call is + // now also required, notifySlideEnded() + // relies on that + // unconditionally. Otherwise, genuine + // shape animations (drawing layer and + // GIF) will not be stopped. + + maListenerContainer.forEach( + [&bReverse]( const uno::Reference< presentation::XSlideShowListener >& xListener ) + { return xListener->slideEnded( bReverse ); } ); +} + +bool SlideShowImpl::notifyHyperLinkClicked( OUString const& hyperLink ) +{ + osl::MutexGuard const guard( m_aMutex ); + + maListenerContainer.forEach( + [&hyperLink]( const uno::Reference< presentation::XSlideShowListener >& xListener ) + { return xListener->hyperLinkClicked( hyperLink ); } ); + return true; +} + +/** Notification from eventmultiplexer that an animation event has occurred. + This will be forwarded to all registered XSlideShoeListener + */ +bool SlideShowImpl::handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) +{ + osl::MutexGuard const guard( m_aMutex ); + + uno::Reference<animations::XAnimationNode> xNode( rNode->getXAnimationNode() ); + + switch( rNode->getState() ) + { + case AnimationNode::ACTIVE: + maListenerContainer.forEach( + [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) + { return xListener->beginEvent( xNode ); } ); + break; + + case AnimationNode::FROZEN: + case AnimationNode::ENDED: + maListenerContainer.forEach( + [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) + { return xListener->endEvent( xNode ); } ); + if(mpCurrentSlide->isPaintOverlayActive()) + mpCurrentSlide->drawPolygons(); + break; + default: + break; + } + + return true; +} + +std::shared_ptr<avmedia::MediaTempFile> SlideShowImpl::getMediaTempFile(const OUString& aUrl) +{ + std::shared_ptr<avmedia::MediaTempFile> aRet; + +#if !HAVE_FEATURE_AVMEDIA + (void)aUrl; +#else + if (!mxSBD.is()) + return aRet; + + comphelper::LifecycleProxy aProxy; + uno::Reference<io::XStream> xStream = + comphelper::OStorageHelper::GetStreamAtPackageURL(mxSBD->getDocumentStorage(), aUrl, + css::embed::ElementModes::READ, aProxy); + + uno::Reference<io::XInputStream> xInStream = xStream->getInputStream(); + if (xInStream.is()) + { + sal_Int32 nLastDot = aUrl.lastIndexOf('.'); + sal_Int32 nLastSlash = aUrl.lastIndexOf('/'); + OUString sDesiredExtension; + if (nLastDot > nLastSlash && nLastDot+1 < aUrl.getLength()) + sDesiredExtension = aUrl.copy(nLastDot); + + OUString sTempUrl; + if (::avmedia::CreateMediaTempFile(xInStream, sTempUrl, sDesiredExtension)) + aRet = std::make_shared<avmedia::MediaTempFile>(sTempUrl); + + xInStream->closeInput(); + } +#endif + + return aRet; +} + +//===== FrameSynchronization ================================================== + +FrameSynchronization::FrameSynchronization (const double nFrameDuration) + : maTimer(), + mnFrameDuration(nFrameDuration), + mnNextFrameTargetTime(0), + mbIsActive(false) +{ + MarkCurrentFrame(); +} + +void FrameSynchronization::MarkCurrentFrame() +{ + mnNextFrameTargetTime = maTimer.getElapsedTime() + mnFrameDuration; +} + +void FrameSynchronization::Synchronize() +{ + if (mbIsActive) + { + // Do busy waiting for now. + for(;;) + { + double remainingTime = mnNextFrameTargetTime - maTimer.getElapsedTime(); + if(remainingTime <= 0) + break; + // Try to sleep most of it. + int remainingMilliseconds = remainingTime * 1000; + if(remainingMilliseconds > 2) + osl::Thread::wait(std::chrono::milliseconds(remainingMilliseconds - 2)); + } + } + + MarkCurrentFrame(); +} + +void FrameSynchronization::Activate() +{ + mbIsActive = true; +} + +void FrameSynchronization::Deactivate() +{ + mbIsActive = false; +} + +} // anon namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +slideshow_SlideShowImpl_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new SlideShowImpl(context)); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/slideview.cxx b/slideshow/source/engine/slideview.cxx new file mode 100644 index 000000000..013e089a0 --- /dev/null +++ b/slideshow/source/engine/slideview.cxx @@ -0,0 +1,1193 @@ +/* -*- 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 <canvas/canvastools.hxx> + +#include <eventqueue.hxx> +#include <eventmultiplexer.hxx> +#include <slideview.hxx> +#include <delayevent.hxx> +#include <unoview.hxx> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/make_shared_from_uno.hxx> + +#include <cppcanvas/spritecanvas.hxx> +#include <cppcanvas/customsprite.hxx> +#include <cppcanvas/vclfactory.hxx> +#include <cppcanvas/basegfxfactory.hxx> + +#include <basegfx/range/b1drange.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> + +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include <memory> +#include <vector> +#include <algorithm> + +using namespace com::sun::star; + +namespace slideshow::internal { + +namespace { + +/** Sprite entry, to store sprite plus priority + + The operator<() defines a strict weak ordering of sprites, sort + key is the sprite priority. + */ +struct SpriteEntry +{ + SpriteEntry( const cppcanvas::CustomSpriteSharedPtr& rSprite, + double nPrio ) : + mpSprite( rSprite ), + mnPriority( nPrio ) + { + } + + bool operator<(const SpriteEntry& rRHS) const + { + return mnPriority < rRHS.mnPriority; + } + + std::weak_ptr< cppcanvas::CustomSprite > mpSprite; + double mnPriority; +}; + +typedef std::vector< SpriteEntry > SpriteVector; + + +/** Create a clip polygon for slide views + + @param rClip + Clip to set (can be empty) + + @param rCanvas + Canvas to create the clip polygon for + + @param rUserSize + The size of the view. Note that the returned clip will + <em>always</em> clip to at least the rect defined herein. + + @return the view clip polygon, in view coordinates, which is + guaranteed to at least clip to the view size. + */ +basegfx::B2DPolyPolygon createClipPolygon( const basegfx::B2DPolyPolygon& rClip, + const cppcanvas::CanvasSharedPtr& /*rCanvas*/, + const basegfx::B2DSize& rUserSize ) +{ + // setup canvas clipping + // ===================== + + // AW: Simplified + const basegfx::B2DRange aClipRange(0, 0, rUserSize.getX(), rUserSize.getY()); + + if(rClip.count()) + { + return basegfx::utils::clipPolyPolygonOnRange(rClip, aClipRange, true, false); + } + else + { + return basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aClipRange)); + } +} + +/** Prepare given clip polygon to be stored as the current clip + + Note that this is separate from createClipPolygon(), to allow + SlideView implementations to store this intermediate result + (createClipPolygon() has to be called every time the view size + changes) + */ +basegfx::B2DPolyPolygon prepareClip( const basegfx::B2DPolyPolygon& rClip ) +{ + basegfx::B2DPolyPolygon aClip( rClip ); + + // normalize polygon, preparation for clipping + // in updateCanvas() + aClip = basegfx::utils::correctOrientations(aClip); + aClip = basegfx::utils::solveCrossovers(aClip); + aClip = basegfx::utils::stripNeutralPolygons(aClip); + aClip = basegfx::utils::stripDispensablePolygons(aClip); + + return aClip; +} + + +void clearRect( ::cppcanvas::CanvasSharedPtr const& pCanvas, + basegfx::B2IRange const& rArea ) +{ + // convert clip polygon to device coordinate system + ::basegfx::B2DPolyPolygon const* pClipPoly( pCanvas->getClip() ); + if( pClipPoly ) + { + ::basegfx::B2DPolyPolygon aClipPoly( *pClipPoly ); + aClipPoly.transform( pCanvas->getTransformation() ); + pCanvas->setClip( aClipPoly ); + } + + // set transformation to identity (->device pixel) + pCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // #i42440# Fill the _full_ background in + // black. Since we had to extend the bitmap by one + // pixel, and the bitmap is initialized white, + // depending on the slide content a one pixel wide + // line will show to the bottom and the right. + const ::basegfx::B2DPolygon aPoly( + ::basegfx::utils::createPolygonFromRect( + basegfx::B2DRange(rArea))); + + ::cppcanvas::PolyPolygonSharedPtr pPolyPoly( + ::cppcanvas::BaseGfxFactory::createPolyPolygon( pCanvas, aPoly ) ); + + if( pPolyPoly ) + { + pPolyPoly->setCompositeOp( css::rendering::CompositeOperation::SOURCE ); + pPolyPoly->setRGBAFillColor( 0xFFFFFF00U ); + pPolyPoly->draw(); + } + +#if defined(DBG_UTIL) + ::cppcanvas::CanvasSharedPtr pCliplessCanvas( pCanvas->clone() ); + pCliplessCanvas->setClip(); + + if( pCanvas->getClip() ) + { + ::cppcanvas::PolyPolygonSharedPtr pPolyPoly2( + ::cppcanvas::BaseGfxFactory::createPolyPolygon( pCliplessCanvas, aPoly )); + if( pPolyPoly2 ) + { + pPolyPoly2->setRGBALineColor( 0x008000FFU ); + pPolyPoly2->draw(); + } + } +#endif +} + +/** Get bounds in pixel + + @param rLayerBounds + Bound rect, in user space coordinates + + @param rTransformation + User space to device pixel transformation + + @return the layer bounds in pixel, extended by one pixel to the + right and bottom + */ +basegfx::B2IRange getLayerBoundsPixel( basegfx::B2DRange const& rLayerBounds, + basegfx::B2DHomMatrix const& rTransformation ) +{ + ::basegfx::B2DRange aTmpRect; + ::canvas::tools::calcTransformedRectBounds( aTmpRect, + rLayerBounds, + rTransformation ); + + if( aTmpRect.isEmpty() ) + return ::basegfx::B2IRange(); + + // #i42440# Returned layer size is one pixel too small, as + // rendering happens one pixel to the right and below the + // actual bound rect. + return ::basegfx::B2IRange( ::basegfx::fround(aTmpRect.getMinX()), + ::basegfx::fround(aTmpRect.getMinY()), + ::basegfx::fround(aTmpRect.getMaxX()) + 1, + ::basegfx::fround(aTmpRect.getMaxY()) + 1 ); +} + + +/** Container class for sprites issued by a ViewLayer + + This class handles the sprite prioritization issues, that are + needed for layer sprites (e.g. the need to re-prioritize sprites + when the layer changes prio). + */ +class LayerSpriteContainer +{ + /** Max fill level of maSprites, before we try to prune it from + deceased sprites + */ + enum{ SPRITE_ULLAGE=256 }; + + /** All sprites that have been issued by this container (pruned + from time to time, for invalid references). This vector is + kept sorted with increasing sprite priority. + */ + SpriteVector maSprites; + + /// Priority of this layer, relative to other view layers + basegfx::B1DRange maLayerPrioRange; + + double getSpritePriority( std::size_t nSpriteNum ) const + { + // divide the available layer range equally between all + // sprites, assign upper bound of individual sprite range as + // sprite prio (the layer itself gets assigned the lower bound + // of sprite 0's individual range): + + // | layer 0 | layer 1 | ... + // | sprite 0 | sprite 1 | sprite 0 | sprite 1 | ... + return maLayerPrioRange.getMinimum() + maLayerPrioRange.getRange()*(nSpriteNum+1)/(maSprites.size()+1); + } + + /** Rescan sprite vector, and remove deceased sprites (and reset + sprite prio) + + @param aBegin + Iterator to the first entry to rescan + */ + void updateSprites() + { + SpriteVector aValidSprites; + + // check all sprites for validity and set new priority + for( const auto& rSprite : maSprites ) + { + cppcanvas::CustomSpriteSharedPtr pCurrSprite( rSprite.mpSprite.lock() ); + + if( pCurrSprite ) + { + // only copy still valid sprites over to the refreshed + // sprite vector. + aValidSprites.push_back( rSprite ); + + pCurrSprite->setPriority( + getSpritePriority( aValidSprites.size()-1 )); + } + } + + // replace sprite list with pruned one + maSprites.swap( aValidSprites ); + } + +public: + LayerSpriteContainer() : + maSprites(), + maLayerPrioRange() + { + } + + const basegfx::B1DRange& getLayerPriority() const + { + return maLayerPrioRange; + } + + void setLayerPriority( const basegfx::B1DRange& rRange ) + { + if( rRange != maLayerPrioRange ) + { + maLayerPrioRange = rRange; + + // prune and recalc sprite prios + updateSprites(); + } + } + + void addSprite( const cppcanvas::CustomSpriteSharedPtr& pSprite, + double nPriority ) + { + if( !pSprite ) + return; + + SpriteEntry aEntry( pSprite,nPriority ); + + // insert new sprite, such that vector stays sorted + SpriteVector::iterator aInsertPos( + maSprites.insert( + std::lower_bound( maSprites.begin(), + maSprites.end(), + aEntry ), + aEntry )); + + const std::size_t nNumSprites( maSprites.size() ); + if( nNumSprites > SPRITE_ULLAGE || + maSprites.end() - aInsertPos > 1 ) + { + // updateSprites() also updates all sprite prios + updateSprites(); + } + else + { + // added sprite to the end, and not too many sprites in + // vector - perform optimized update (only need to set + // prio). This basically caters for the common case of + // iterated character animations, which generate lots of + // sprites, all added to the end. + pSprite->setPriority( + getSpritePriority( nNumSprites-1 )); + } + } + + void clear() + { + maSprites.clear(); + } +}; + + +/** This class provides layers for a slide view + + Layers are used to render animations with the correct z order - + because sprites are always in front of the static canvas + background, shapes that must appear <em<before</em> an animation + must also be displayed as a sprite. + + Each layer has a priority assigned to it (valid range [0,1]), which + also affects all sprites created for this specific layer - i.e. if + the layer priority changes, the sprites change z order together + with their parent. + */ +class SlideViewLayer : public ViewLayer +{ + /// Smart container for all sprites issued by this layer + mutable LayerSpriteContainer maSpriteContainer; + + /// Bounds of this layer in user space coordinates + basegfx::B2DRange maLayerBounds; + + /// Bounds of this layer in device pixel + mutable basegfx::B2IRange maLayerBoundsPixel; + + /// Current clip polygon in user coordinates + basegfx::B2DPolyPolygon maClip; + + /// Current size of the view in user coordinates + basegfx::B2DSize maUserSize; + + /// Current overall view transformation + basegfx::B2DHomMatrix maTransformation; + + /// 'parent' canvas, this viewlayer is associated with + const cppcanvas::SpriteCanvasSharedPtr mpSpriteCanvas; + + /** output surface (necessarily a sprite, won't otherwise be able + to display anything <em>before</em> other sprites) + */ + mutable cppcanvas::CustomSpriteSharedPtr mpSprite; + + /// actual output canvas retrieved from a sprite + mutable cppcanvas::CanvasSharedPtr mpOutputCanvas; + + /// ptr back to owning view. needed for isOnView() method + View const* const mpParentView; + +public: + /** Create a new layer + + @param pCanvas + Sprite canvas to create the layer on + + @param rTransform + Initial overall canvas transformation + + @param rLayerBounds + Initial layer bounds, in view coordinate system + */ + SlideViewLayer( const cppcanvas::SpriteCanvasSharedPtr& pCanvas, + const basegfx::B2DHomMatrix& rTransform, + const basegfx::B2DRange& rLayerBounds, + const basegfx::B2DSize& rUserSize, + View const* const pParentView) : + maSpriteContainer(), + maLayerBounds(rLayerBounds), + maLayerBoundsPixel(), + maClip(), + maUserSize(rUserSize), + maTransformation(rTransform), + mpSpriteCanvas(pCanvas), + mpSprite(), + mpOutputCanvas(), + mpParentView(pParentView) + { + } + + SlideViewLayer(const SlideViewLayer&) = delete; + SlideViewLayer& operator=(const SlideViewLayer&) = delete; + + void updateView( const basegfx::B2DHomMatrix& rMatrix, + const basegfx::B2DSize& rUserSize ) + { + maTransformation = rMatrix; + maUserSize = rUserSize; + + // limit layer bounds to visible screen + maLayerBounds.intersect( basegfx::B2DRange(0.0, + 0.0, + maUserSize.getX(), + maUserSize.getY()) ); + + basegfx::B2IRange const& rNewLayerPixel( + getLayerBoundsPixel(maLayerBounds, + maTransformation) ); + if( rNewLayerPixel != maLayerBoundsPixel ) + { + // re-gen sprite with new size + mpOutputCanvas.reset(); + mpSprite.reset(); + } + } + + virtual css::geometry::IntegerSize2D getTranslationOffset() const override + { + basegfx::B2DRectangle aTmpRect; + canvas::tools::calcTransformedRectBounds( aTmpRect, + maLayerBounds, + maTransformation ); + geometry::IntegerSize2D offset(0, 0); + + // Add translation according to the origin of aTmpRect. Ignore the + // translation when aTmpRect was not properly initialized. + if ( ! aTmpRect.isEmpty()) + { + offset.Width = basegfx::fround(aTmpRect.getMinX()); + offset.Height = basegfx::fround(aTmpRect.getMinY()); + } + return offset; + } + +private: + // ViewLayer interface + + + virtual cppcanvas::CustomSpriteSharedPtr createSprite( + const ::basegfx::B2DSize& rSpriteSizePixel, + double nPriority ) const override + { + cppcanvas::CustomSpriteSharedPtr pSprite( + mpSpriteCanvas->createCustomSprite( rSpriteSizePixel ) ); + + maSpriteContainer.addSprite( pSprite, + nPriority ); + + return pSprite; + } + + virtual void setPriority( const basegfx::B1DRange& rRange ) override + { + OSL_ENSURE( !rRange.isEmpty() && + rRange.getMinimum() >= 1.0, + "SlideViewLayer::setPriority(): prio MUST be larger than 1.0 (because " + "the background layer already lies there)" ); + + maSpriteContainer.setLayerPriority( rRange ); + + if( mpSprite ) + mpSprite->setPriority( rRange.getMinimum() ); + } + + virtual basegfx::B2DHomMatrix getTransformation() const override + { + // Offset given transformation by left, top border of given + // range (after transformation through given transformation) + basegfx::B2DRectangle aTmpRect; + canvas::tools::calcTransformedRectBounds( aTmpRect, + maLayerBounds, + maTransformation ); + + basegfx::B2DHomMatrix aMatrix( maTransformation ); + + // Add translation according to the origin of aTmpRect. Ignore the + // translation when aTmpRect was not properly initialized. + if ( ! aTmpRect.isEmpty()) + { + aMatrix.translate( -basegfx::fround(aTmpRect.getMinX()), + -basegfx::fround(aTmpRect.getMinY()) ); + } + + return aMatrix; + } + + virtual basegfx::B2DHomMatrix getSpriteTransformation() const override + { + return maTransformation; + } + + virtual void clear() const override + { + // grab canvas - that also lazy-initializes maLayerBoundsPixel + cppcanvas::CanvasSharedPtr pCanvas=getCanvas()->clone(); + + // clear whole canvas + const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange()); + clearRect(pCanvas, + basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.getY())); + } + + virtual void clearAll() const override + { + // grab canvas - that also lazy-initializes maLayerBoundsPixel + ::cppcanvas::CanvasSharedPtr pCanvas( getCanvas()->clone() ); + + // clear layer clip, to clear whole area + pCanvas->setClip(); + + // clear whole canvas + const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange()); + clearRect(pCanvas, + basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.getY())); + } + + virtual bool isOnView(ViewSharedPtr const& rView) const override + { + return rView.get() == mpParentView; + } + + virtual cppcanvas::CanvasSharedPtr getCanvas() const override + { + if( !mpOutputCanvas ) + { + if( !mpSprite ) + { + maLayerBoundsPixel = getLayerBoundsPixel(maLayerBounds, + maTransformation); + + // HACK: ensure at least 1x1 pixel size. clients might + // need an actual canvas (e.g. for bound rect + // calculations) without rendering anything. Better + // solution: introduce something like a reference + // canvas for ViewLayers, which is always available. + if( maLayerBoundsPixel.isEmpty() ) + maLayerBoundsPixel = basegfx::B2IRange(0,0,1,1); + + const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange()); + mpSprite = mpSpriteCanvas->createCustomSprite( + basegfx::B2DVector(sal::static_int_cast<sal_Int32>(rSpriteSize.getX()), + sal::static_int_cast<sal_Int32>(rSpriteSize.getY())) ); + + mpSprite->setPriority( + maSpriteContainer.getLayerPriority().getMinimum() ); + +#if defined(DBG_UTIL) + mpSprite->movePixel( + basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) + + basegfx::B2DPoint(10,10) ); + + mpSprite->setAlpha(0.5); +#else + mpSprite->movePixel( + basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) ); + + mpSprite->setAlpha(1.0); +#endif + mpSprite->show(); + } + + ENSURE_OR_THROW( mpSprite, + "SlideViewLayer::getCanvas(): no layer sprite" ); + + mpOutputCanvas = mpSprite->getContentCanvas(); + + ENSURE_OR_THROW( mpOutputCanvas, + "SlideViewLayer::getCanvas(): sprite doesn't yield a canvas" ); + + // new canvas retrieved - setup transformation and clip + mpOutputCanvas->setTransformation( getTransformation() ); + mpOutputCanvas->setClip( + createClipPolygon( maClip, + mpOutputCanvas, + maUserSize )); + } + + return mpOutputCanvas; + } + + virtual void setClip( const basegfx::B2DPolyPolygon& rClip ) override + { + basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip ); + + if( aNewClip != maClip ) + { + maClip = aNewClip; + + if(mpOutputCanvas ) + mpOutputCanvas->setClip( + createClipPolygon( maClip, + mpOutputCanvas, + maUserSize )); + } + } + + virtual bool resize( const ::basegfx::B2DRange& rArea ) override + { + const bool bRet( maLayerBounds != rArea ); + maLayerBounds = rArea; + updateView( maTransformation, + maUserSize ); + + return bRet; + } +}; + + +typedef cppu::WeakComponentImplHelper< + css::util::XModifyListener, + css::awt::XPaintListener> SlideViewBase; + +/** SlideView class + + This class implements the View interface, encapsulating + <em>one</em> view a slideshow is displayed on. + */ +class SlideView : private cppu::BaseMutex, + public SlideViewBase, + public UnoView +{ +public: + SlideView( const uno::Reference<presentation::XSlideShowView>& xView, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer ); + void updateCanvas(); + +private: + // View: + virtual ViewLayerSharedPtr createViewLayer( const basegfx::B2DRange& rLayerBounds ) const override; + virtual bool updateScreen() const override; + virtual bool paintScreen() const override; + virtual void setViewSize( const ::basegfx::B2DSize& ) override; + virtual void setCursorShape( sal_Int16 nPointerShape ) override; + + // ViewLayer interface + virtual bool isOnView(ViewSharedPtr const& rView) const override; + virtual void clear() const override; + virtual void clearAll() const override; + virtual cppcanvas::CanvasSharedPtr getCanvas() const override; + virtual cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& rSpriteSizePixel, + double nPriority ) const override; + virtual void setPriority( const basegfx::B1DRange& rRange ) override; + virtual geometry::IntegerSize2D getTranslationOffset() const override; + virtual ::basegfx::B2DHomMatrix getTransformation() const override; + virtual basegfx::B2DHomMatrix getSpriteTransformation() const override; + virtual void setClip( const ::basegfx::B2DPolyPolygon& rClip ) override; + virtual bool resize( const ::basegfx::B2DRange& rArea ) override; + + // UnoView: + virtual void _dispose() override; + virtual uno::Reference<presentation::XSlideShowView> getUnoView()const override; + virtual void setIsSoundEnabled (const bool bValue) override; + virtual bool isSoundEnabled() const override; + + // XEventListener: + virtual void SAL_CALL disposing( lang::EventObject const& evt ) override; + // XModifyListener: + virtual void SAL_CALL modified( const lang::EventObject& aEvent ) override; + // XPaintListener: + virtual void SAL_CALL windowPaint( const awt::PaintEvent& e ) override; + + // WeakComponentImplHelperBase: + virtual void SAL_CALL disposing() override; + + void updateClip(); + +private: + typedef std::vector< std::weak_ptr<SlideViewLayer> > ViewLayerVector; + + /// Prune viewlayers from deceased ones, optionally update them + void pruneLayers( bool bWithViewLayerUpdate=false ) const; + + /** Max fill level of maViewLayers, before we try to prune it from + deceased layers + */ + enum{ LAYER_ULLAGE=8 }; + + uno::Reference<presentation::XSlideShowView> mxView; + cppcanvas::SpriteCanvasSharedPtr mpCanvas; + + EventMultiplexer& mrEventMultiplexer; + EventQueue& mrEventQueue; + + mutable LayerSpriteContainer maSprites; + mutable ViewLayerVector maViewLayers; + + basegfx::B2DPolyPolygon maClip; + + basegfx::B2DHomMatrix maViewTransform; + basegfx::B2DSize maUserSize; + bool mbIsSoundEnabled; +}; + + +SlideView::SlideView( const uno::Reference<presentation::XSlideShowView>& xView, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer ) : + SlideViewBase( m_aMutex ), + mxView( xView ), + mpCanvas(), + mrEventMultiplexer( rEventMultiplexer ), + mrEventQueue( rEventQueue ), + maSprites(), + maViewLayers(), + maClip(), + maViewTransform(), + maUserSize( 1.0, 1.0 ), // default size: one-by-one rectangle + mbIsSoundEnabled(true) +{ + // take care not constructing any UNO references to this _inside_ + // ctor, shift that code to createSlideView()! + ENSURE_OR_THROW( mxView.is(), + "SlideView::SlideView(): Invalid view" ); + + mpCanvas = cppcanvas::VCLFactory::createSpriteCanvas( + xView->getCanvas() ); + ENSURE_OR_THROW( mpCanvas, + "Could not create cppcanvas" ); + + geometry::AffineMatrix2D aViewTransform( + xView->getTransformation() ); + + if( basegfx::fTools::equalZero( + basegfx::B2DVector(aViewTransform.m00, + aViewTransform.m10).getLength()) || + basegfx::fTools::equalZero( + basegfx::B2DVector(aViewTransform.m01, + aViewTransform.m11).getLength()) ) + { + OSL_FAIL( "SlideView::SlideView(): Singular matrix!" ); + + canvas::tools::setIdentityAffineMatrix2D(aViewTransform); + } + + basegfx::unotools::homMatrixFromAffineMatrix( + maViewTransform, aViewTransform ); + + // once and forever: set fixed prio to this 'layer' (we're always + // the background layer) + maSprites.setLayerPriority( basegfx::B1DRange(0.0,1.0) ); +} + +void SlideView::disposing() +{ + osl::MutexGuard aGuard( m_aMutex ); + + maViewLayers.clear(); + maSprites.clear(); + mpCanvas.reset(); + + // additionally, also de-register from XSlideShowView + if (mxView.is()) + { + mxView->removeTransformationChangedListener( this ); + mxView->removePaintListener( this ); + mxView.clear(); + } +} + +ViewLayerSharedPtr SlideView::createViewLayer( const basegfx::B2DRange& rLayerBounds ) const +{ + osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_THROW( mpCanvas, + "SlideView::createViewLayer(): Disposed" ); + + const std::size_t nNumLayers( maViewLayers.size() ); + + // avoid filling up layer vector with lots of deceased layer weak + // ptrs + if( nNumLayers > LAYER_ULLAGE ) + pruneLayers(); + + auto xViewLayer = std::make_shared<SlideViewLayer>(mpCanvas, + getTransformation(), + rLayerBounds, + maUserSize, + this); + maViewLayers.push_back(xViewLayer); + + return xViewLayer; +} + +bool SlideView::updateScreen() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_RETURN_FALSE( mpCanvas, + "SlideView::updateScreen(): Disposed" ); + + return mpCanvas->updateScreen( false ); +} + +bool SlideView::paintScreen() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_RETURN_FALSE( mpCanvas, + "SlideView::paintScreen(): Disposed" ); + + return mpCanvas->updateScreen( true ); +} + +void SlideView::clear() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + OSL_ENSURE( mxView.is() && mpCanvas, + "SlideView::clear(): Disposed" ); + if( !mxView.is() || !mpCanvas ) + return; + + // keep layer clip + clearRect(getCanvas()->clone(), + getLayerBoundsPixel( + basegfx::B2DRange(0,0, + maUserSize.getX(), + maUserSize.getY()), + getTransformation())); +} + +void SlideView::clearAll() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + OSL_ENSURE( mxView.is() && mpCanvas, + "SlideView::clear(): Disposed" ); + if( !mxView.is() || !mpCanvas ) + return; + + mpCanvas->clear(); // this is unnecessary, strictly speaking. but + // it makes the SlideView behave exactly like a + // sprite-based SlideViewLayer, because those + // are created from scratch after a resize + + // clear whole view + mxView->clear(); +} + +void SlideView::setViewSize( const basegfx::B2DSize& rSize ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + maUserSize = rSize; + updateCanvas(); +} + +void SlideView::setCursorShape( sal_Int16 nPointerShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (mxView.is()) + mxView->setMouseCursor( nPointerShape ); +} + +bool SlideView::isOnView(ViewSharedPtr const& rView) const +{ + return rView.get() == this; +} + +cppcanvas::CanvasSharedPtr SlideView::getCanvas() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_THROW( mpCanvas, + "SlideView::getCanvas(): Disposed" ); + + return mpCanvas; +} + +cppcanvas::CustomSpriteSharedPtr SlideView::createSprite( + const basegfx::B2DSize& rSpriteSizePixel, + double nPriority ) const +{ + osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_THROW( mpCanvas, "SlideView::createSprite(): Disposed" ); + + cppcanvas::CustomSpriteSharedPtr pSprite( + mpCanvas->createCustomSprite( rSpriteSizePixel ) ); + + maSprites.addSprite( pSprite, + nPriority ); + + return pSprite; +} + +void SlideView::setPriority( const basegfx::B1DRange& /*rRange*/ ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + OSL_FAIL( "SlideView::setPriority() is a NOOP for slide view - " + "content will always be shown in the background" ); +} + +basegfx::B2DHomMatrix SlideView::getTransformation() const +{ + osl::MutexGuard aGuard( m_aMutex ); + + basegfx::B2DHomMatrix aMatrix; + aMatrix.scale( 1.0/maUserSize.getX(), 1.0/maUserSize.getY() ); + + return maViewTransform * aMatrix; +} + +geometry::IntegerSize2D SlideView::getTranslationOffset() const +{ + return mxView->getTranslationOffset(); +} + +basegfx::B2DHomMatrix SlideView::getSpriteTransformation() const +{ + return getTransformation(); +} + +void SlideView::setClip( const basegfx::B2DPolyPolygon& rClip ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip ); + + if( aNewClip != maClip ) + { + maClip = aNewClip; + + updateClip(); + } +} + +bool SlideView::resize( const ::basegfx::B2DRange& /*rArea*/ ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + OSL_FAIL( "SlideView::resize(): ignored for the View, can't change size " + "effectively, anyway" ); + + return false; +} + +uno::Reference<presentation::XSlideShowView> SlideView::getUnoView() const +{ + osl::MutexGuard aGuard( m_aMutex ); + return mxView; +} + +void SlideView::setIsSoundEnabled (const bool bValue) +{ + mbIsSoundEnabled = bValue; +} + +bool SlideView::isSoundEnabled() const +{ + return mbIsSoundEnabled; +} + +void SlideView::_dispose() +{ + dispose(); +} + +// XEventListener +void SlideView::disposing( lang::EventObject const& evt ) +{ + // no deregistration necessary anymore, XView has left: + osl::MutexGuard const guard( m_aMutex ); + + if (mxView.is()) + { + OSL_ASSERT( evt.Source == mxView ); + mxView.clear(); + } + + dispose(); +} + +// silly wrapper to check that event handlers don't touch dead SlideView +struct WeakRefWrapper +{ + SlideView & m_rObj; + uno::WeakReference<uno::XInterface> const m_wObj; + std::function<void (SlideView&)> const m_func; + + WeakRefWrapper(SlideView & rObj, std::function<void (SlideView&)> const& func) + : m_rObj(rObj) + , m_wObj(static_cast<::cppu::OWeakObject*>(&rObj)) + , m_func(func) + { + } + + void operator()() + { + uno::Reference<uno::XInterface> const xObj(m_wObj); + if (xObj.is()) + { + m_func(m_rObj); + } + } +}; + +// XModifyListener +void SlideView::modified( const lang::EventObject& /*aEvent*/ ) +{ + osl::MutexGuard const guard( m_aMutex ); + + OSL_ENSURE( mxView.is(), "SlideView::modified(): " + "Disposed, but event received from XSlideShowView?!"); + + if( !mxView.is() ) + return; + + geometry::AffineMatrix2D aViewTransform( + mxView->getTransformation() ); + + if( basegfx::fTools::equalZero( + basegfx::B2DVector(aViewTransform.m00, + aViewTransform.m10).getLength()) || + basegfx::fTools::equalZero( + basegfx::B2DVector(aViewTransform.m01, + aViewTransform.m11).getLength()) ) + { + OSL_FAIL( "SlideView::modified(): Singular matrix!" ); + + canvas::tools::setIdentityAffineMatrix2D(aViewTransform); + } + + // view transformation really changed? + basegfx::B2DHomMatrix aNewTransform; + basegfx::unotools::homMatrixFromAffineMatrix( + aNewTransform, + aViewTransform ); + + if( aNewTransform == maViewTransform ) + return; // No change, nothing to do + + maViewTransform = aNewTransform; + + updateCanvas(); + + // notify view change. Don't call EventMultiplexer directly, this + // might not be the main thread! + mrEventQueue.addEvent( + makeEvent( WeakRefWrapper(*this, + [] (SlideView & rThis) { rThis.mrEventMultiplexer.notifyViewChanged(rThis.mxView); }), + "EventMultiplexer::notifyViewChanged")); +} + +// XPaintListener +void SlideView::windowPaint( const awt::PaintEvent& /*e*/ ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + OSL_ENSURE( mxView.is() && mpCanvas, "Disposed, but event received?!" ); + + // notify view clobbering. Don't call EventMultiplexer directly, + // this might not be the main thread! + mrEventQueue.addEvent( + makeEvent( WeakRefWrapper(*this, + [] (SlideView & rThis) { rThis.mrEventMultiplexer.notifyViewClobbered(rThis.mxView); }), + "EventMultiplexer::notifyViewClobbered") ); +} + +void SlideView::updateCanvas() +{ + OSL_ENSURE( mpCanvas, + "SlideView::updateCanvasTransform(): Disposed" ); + + if( !mpCanvas || !mxView.is()) + return; + + clearAll(); + mpCanvas->setTransformation( getTransformation() ); + mpCanvas->setClip( + createClipPolygon( maClip, + mpCanvas, + maUserSize )); + + // forward update to viewlayers + pruneLayers( true ); +} + +void SlideView::updateClip() +{ + OSL_ENSURE( mpCanvas, + "SlideView::updateClip(): Disposed" ); + + if( !mpCanvas ) + return; + + mpCanvas->setClip( + createClipPolygon( maClip, + mpCanvas, + maUserSize )); + + pruneLayers(); +} + +void SlideView::pruneLayers( bool bWithViewLayerUpdate ) const +{ + ViewLayerVector aValidLayers; + + const basegfx::B2DHomMatrix& rCurrTransform( + getTransformation() ); + + // check all layers for validity, and retain only the live ones + for( const auto& rView : maViewLayers ) + { + std::shared_ptr< SlideViewLayer > xCurrLayer( rView.lock() ); + + if ( xCurrLayer ) + { + aValidLayers.push_back( xCurrLayer ); + + if( bWithViewLayerUpdate ) + xCurrLayer->updateView( rCurrTransform, + maUserSize ); + } + } + + // replace layer list with pruned one + maViewLayers.swap( aValidLayers ); +} + +} // anonymous namespace + +UnoViewSharedPtr createSlideView( uno::Reference< presentation::XSlideShowView> const& xView, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer ) +{ + std::shared_ptr<SlideView> const that( + comphelper::make_shared_from_UNO( + new SlideView(xView, + rEventQueue, + rEventMultiplexer))); + + // register listeners with XSlideShowView + xView->addTransformationChangedListener( that.get() ); + xView->addPaintListener( that.get() ); + + // set new transformation + that->updateCanvas(); + + return that; +} + +} // namespace slideshow + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/smilfunctionparser.cxx b/slideshow/source/engine/smilfunctionparser.cxx new file mode 100644 index 000000000..06a2b9639 --- /dev/null +++ b/slideshow/source/engine/smilfunctionparser.cxx @@ -0,0 +1,628 @@ +/* -*- 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 <slideshowexceptions.hxx> +#include <smilfunctionparser.hxx> +#include <expressionnodefactory.hxx> + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +// Makes parser a static resource, +// we're synchronized externally. +// But watch out, the parser might have +// state not visible to this code! +#define BOOST_SPIRIT_SINGLE_GRAMMAR_INSTANCE + +#if defined(DBG_UTIL) +#define BOOST_SPIRIT_DEBUG +#endif +#include <boost/spirit/include/classic_core.hpp> + +#include <iostream> +#include <functional> +#include <algorithm> +#include <stack> + + +/* Implementation of SmilFunctionParser class */ + +namespace slideshow::internal +{ + namespace + { + typedef const char* StringIteratorT; + + struct ParserContext + { + typedef ::std::stack< std::shared_ptr<ExpressionNode> > OperandStack; + + // stores a stack of not-yet-evaluated operands. This is used + // by the operators (i.e. '+', '*', 'sin' etc.) to pop their + // arguments from. If all arguments to an operator are constant, + // the operator pushes a precalculated result on the stack, and + // a composite ExpressionNode otherwise. + OperandStack maOperandStack; + + // bounds of the shape this expression is associated with + ::basegfx::B2DRectangle maShapeBounds; + + // when true, enable usage of time-dependent variable '$' + // in expressions + bool mbParseAnimationFunction; + }; + + typedef ::std::shared_ptr< ParserContext > ParserContextSharedPtr; + + + template< typename Generator > class ShapeBoundsFunctor + { + public: + ShapeBoundsFunctor( Generator aGenerator, + const ParserContextSharedPtr& rContext ) : + maGenerator( aGenerator ), + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "ShapeBoundsFunctor::ShapeBoundsFunctor(): Invalid context" ); + } + + void operator()( StringIteratorT, StringIteratorT ) const + { + mpContext->maOperandStack.push( + ExpressionNodeFactory::createConstantValueExpression( + maGenerator( mpContext->maShapeBounds ) ) ); + } + + private: + Generator maGenerator; + ParserContextSharedPtr mpContext; + }; + + template< typename Generator > ShapeBoundsFunctor< Generator > + makeShapeBoundsFunctor( const Generator& rGenerator, + const ParserContextSharedPtr& rContext ) + { + return ShapeBoundsFunctor<Generator>(rGenerator, rContext); + } + + /** Generate apriori constant value + */ + class ConstantFunctor + { + public: + ConstantFunctor( double rValue, + const ParserContextSharedPtr& rContext ) : + mnValue( rValue ), + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "ConstantFunctor::ConstantFunctor(): Invalid context" ); + } + + void operator()( StringIteratorT, StringIteratorT ) const + { + mpContext->maOperandStack.push( + ExpressionNodeFactory::createConstantValueExpression( mnValue ) ); + } + + private: + const double mnValue; + ParserContextSharedPtr mpContext; + }; + + /** Generate parse-dependent-but-then-constant value + */ + class DoubleConstantFunctor + { + public: + explicit DoubleConstantFunctor( const ParserContextSharedPtr& rContext ) : + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "DoubleConstantFunctor::DoubleConstantFunctor(): Invalid context" ); + } + + void operator()( double n ) const + { + // push constant value expression to the stack + mpContext->maOperandStack.push( + ExpressionNodeFactory::createConstantValueExpression( n ) ); + } + + private: + ParserContextSharedPtr mpContext; + }; + + /** Generate special t value expression node + */ + class ValueTFunctor + { + public: + explicit ValueTFunctor( const ParserContextSharedPtr& rContext ) : + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "ValueTFunctor::ValueTFunctor(): Invalid context" ); + } + + void operator()( StringIteratorT, StringIteratorT ) const + { + if( !mpContext->mbParseAnimationFunction ) + { + SAL_WARN("slideshow", "ValueTFunctor::operator(): variable encountered, but we're not parsing a function here" ); + throw ParseError(); + } + + // push special t value expression to the stack + mpContext->maOperandStack.push( + ExpressionNodeFactory::createValueTExpression() ); + } + + private: + ParserContextSharedPtr mpContext; + }; + + template< typename Functor > class UnaryFunctionFunctor + { + private: + /** ExpressionNode implementation for unary + function over one ExpressionNode + */ + class UnaryFunctionExpression : public ExpressionNode + { + public: + UnaryFunctionExpression( const Functor& rFunctor, + const std::shared_ptr<ExpressionNode>& rArg ) : + maFunctor( rFunctor ), + mpArg( rArg ) + { + } + + virtual double operator()( double t ) const override + { + return maFunctor( (*mpArg)(t) ); + } + + virtual bool isConstant() const override + { + return mpArg->isConstant(); + } + + private: + Functor maFunctor; + std::shared_ptr<ExpressionNode> mpArg; + }; + + public: + UnaryFunctionFunctor( const Functor& rFunctor, + const ParserContextSharedPtr& rContext ) : + maFunctor( rFunctor ), + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "UnaryFunctionFunctor::UnaryFunctionFunctor(): Invalid context" ); + } + + void operator()( StringIteratorT, StringIteratorT ) const + { + ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); + + if( rNodeStack.empty() ) + throw ParseError( "Not enough arguments for unary operator" ); + + // retrieve arguments + std::shared_ptr<ExpressionNode> pArg( std::move(rNodeStack.top()) ); + rNodeStack.pop(); + + // check for constness + if( pArg->isConstant() ) + { + rNodeStack.push( + ExpressionNodeFactory::createConstantValueExpression( + maFunctor( (*pArg)(0.0) ) ) ); + } + else + { + // push complex node, that calcs the value on demand + rNodeStack.push( + std::make_shared<UnaryFunctionExpression>( + maFunctor, + pArg ) ); + } + } + + private: + Functor maFunctor; + ParserContextSharedPtr mpContext; + }; + + // TODO(Q2): Refactor makeUnaryFunctionFunctor, + // makeBinaryFunctionFunctor and the whole + // ExpressionNodeFactory, to use a generic + // makeFunctionFunctor template, which is overloaded for + // unary, binary, ternary, etc. function pointers. + template< typename Functor > UnaryFunctionFunctor<Functor> + makeUnaryFunctionFunctor( const Functor& rFunctor, + const ParserContextSharedPtr& rContext ) + { + return UnaryFunctionFunctor<Functor>( rFunctor, rContext ); + } + + // MSVC has problems instantiating above template function with plain function + // pointers (doesn't like the const reference there). Thus, provide it with + // a dedicated overload here. + UnaryFunctionFunctor< double (*)(double) > + makeUnaryFunctionFunctor( double (*pFunc)(double), + const ParserContextSharedPtr& rContext ) + { + return UnaryFunctionFunctor< double (*)(double) >( pFunc, rContext ); + } + + /** Implements a binary function over two ExpressionNodes + + @tpl Generator + Generator functor, to generate an ExpressionNode of + appropriate type + + */ + template< class Generator > class BinaryFunctionFunctor + { + public: + BinaryFunctionFunctor( const Generator& rGenerator, + const ParserContextSharedPtr& rContext ) : + maGenerator( rGenerator ), + mpContext( rContext ) + { + ENSURE_OR_THROW( mpContext, + "BinaryFunctionFunctor::BinaryFunctionFunctor(): Invalid context" ); + } + + void operator()( StringIteratorT, StringIteratorT ) const + { + ParserContext::OperandStack& rNodeStack( mpContext->maOperandStack ); + + if( rNodeStack.size() < 2 ) + throw ParseError( "Not enough arguments for binary operator" ); + + // retrieve arguments + std::shared_ptr<ExpressionNode> pSecondArg( std::move(rNodeStack.top()) ); + rNodeStack.pop(); + std::shared_ptr<ExpressionNode> pFirstArg( std::move(rNodeStack.top()) ); + rNodeStack.pop(); + + // create combined ExpressionNode + std::shared_ptr<ExpressionNode> pNode( maGenerator( pFirstArg, + pSecondArg ) ); + + assert(pSecondArg && pFirstArg); + + // check for constness + if (pFirstArg->isConstant() && pSecondArg->isConstant()) + { + // call the operator() at pNode, store result + // in constant value ExpressionNode. + rNodeStack.push( + ExpressionNodeFactory::createConstantValueExpression( + (*pNode)( 0.0 ) ) ); + } + else + { + // push complex node, that calcs the value on demand + rNodeStack.push( pNode ); + } + } + + private: + Generator maGenerator; + ParserContextSharedPtr mpContext; + }; + + template< typename Generator > BinaryFunctionFunctor<Generator> + makeBinaryFunctionFunctor( const Generator& rGenerator, + const ParserContextSharedPtr& rContext ) + { + return BinaryFunctionFunctor<Generator>( rGenerator, rContext ); + } + + + // Workaround for MSVC compiler anomaly (stack trashing) + + // The default ureal_parser_policies implementation of parse_exp + // triggers a really weird error in MSVC7 (Version 13.00.9466), in + // that the real_parser_impl::parse_main() call of parse_exp() + // overwrites the frame pointer _on the stack_ (EBP of the calling + // function gets overwritten while lying on the stack). + + // For the time being, our parser thus can only read the 1.0E10 + // notation, not the 1.0e10 one. + + // TODO(F1): Also handle the 1.0e10 case here. + template< typename T > struct custom_real_parser_policies : public ::boost::spirit::classic::ureal_parser_policies<T> + { + template< typename ScannerT > + static typename ::boost::spirit::classic::parser_result< ::boost::spirit::classic::chlit<>, ScannerT >::type + parse_exp(ScannerT& scan) + { + // as_lower_d somehow breaks MSVC7 + return ::boost::spirit::classic::ch_p('E').parse(scan); + } + }; + + /* This class implements the following grammar (more or + less literally written down below, only slightly + obfuscated by the parser actions): + + identifier = '$'|'pi'|'e'|'X'|'Y'|'Width'|'Height' + + function = 'abs'|'sqrt'|'sin'|'cos'|'tan'|'atan'|'acos'|'asin'|'exp'|'log' + + basic_expression = + number | + identifier | + function '(' additive_expression ')' | + '(' additive_expression ')' + + unary_expression = + '-' basic_expression | + basic_expression + + multiplicative_expression = + unary_expression ( ( '*' unary_expression )* | + ( '/' unary_expression )* ) + + additive_expression = + multiplicative_expression ( ( '+' multiplicative_expression )* | + ( '-' multiplicative_expression )* ) + + */ + class ExpressionGrammar : public ::boost::spirit::classic::grammar< ExpressionGrammar > + { + public: + /** Create an arithmetic expression grammar + + @param rParserContext + Contains context info for the parser + */ + explicit ExpressionGrammar( const ParserContextSharedPtr& rParserContext ) : + mpParserContext( rParserContext ) + { + } + + template< typename ScannerT > class definition + { + public: + // grammar definition + explicit definition( const ExpressionGrammar& self ) + { + using ::boost::spirit::classic::str_p; + using ::boost::spirit::classic::real_parser; + + identifier = + str_p( "$" )[ ValueTFunctor( self.getContext()) ] + | str_p( "pi" )[ ConstantFunctor(M_PI, self.getContext()) ] + | str_p( "e" )[ ConstantFunctor(M_E, self.getContext()) ] + | str_p( "x" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getCenterX),self.getContext()) ] + | str_p( "y" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getCenterY),self.getContext()) ] + | str_p( "width" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getWidth), self.getContext()) ] + | str_p( "height" )[ makeShapeBoundsFunctor(::std::mem_fn(&::basegfx::B2DRange::getHeight), self.getContext()) ] + ; + + unaryFunction = + (str_p( "abs" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&fabs, self.getContext()) ] + | (str_p( "sqrt" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sqrt, self.getContext()) ] + | (str_p( "sin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&sin, self.getContext()) ] + | (str_p( "cos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&cos, self.getContext()) ] + | (str_p( "tan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&tan, self.getContext()) ] + | (str_p( "atan" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&atan, self.getContext()) ] + | (str_p( "acos" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&acos, self.getContext()) ] + | (str_p( "asin" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&asin, self.getContext()) ] + | (str_p( "exp" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&exp, self.getContext()) ] + | (str_p( "log" ) >> '(' >> additiveExpression >> ')' )[ makeUnaryFunctionFunctor(&log, self.getContext()) ] + ; + + binaryFunction = + (str_p( "min" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinExpression, self.getContext()) ] + | (str_p( "max" ) >> '(' >> additiveExpression >> ',' >> additiveExpression >> ')' )[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMaxExpression, self.getContext()) ] + ; + + basicExpression = + real_parser<double, custom_real_parser_policies<double> >()[ DoubleConstantFunctor(self.getContext()) ] + | identifier + | unaryFunction + | binaryFunction + | '(' >> additiveExpression >> ')' + ; + + unaryExpression = + ('-' >> basicExpression)[ makeUnaryFunctionFunctor(::std::negate<double>(), self.getContext()) ] + | basicExpression + ; + + multiplicativeExpression = + unaryExpression + >> *( ('*' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMultipliesExpression, self.getContext()) ] + | ('/' >> unaryExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createDividesExpression, self.getContext()) ] + ) + ; + + additiveExpression = + multiplicativeExpression + >> *( ('+' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createPlusExpression, self.getContext()) ] + | ('-' >> multiplicativeExpression)[ makeBinaryFunctionFunctor(&ExpressionNodeFactory::createMinusExpression, self.getContext()) ] + ) + ; + + BOOST_SPIRIT_DEBUG_RULE(additiveExpression); + BOOST_SPIRIT_DEBUG_RULE(multiplicativeExpression); + BOOST_SPIRIT_DEBUG_RULE(unaryExpression); + BOOST_SPIRIT_DEBUG_RULE(basicExpression); + BOOST_SPIRIT_DEBUG_RULE(unaryFunction); + BOOST_SPIRIT_DEBUG_RULE(binaryFunction); + BOOST_SPIRIT_DEBUG_RULE(identifier); + } + + const ::boost::spirit::classic::rule< ScannerT >& start() const + { + return additiveExpression; + } + + private: + // the constituents of the Spirit arithmetic expression grammar. + // For the sake of readability, without 'ma' prefix. + ::boost::spirit::classic::rule< ScannerT > additiveExpression; + ::boost::spirit::classic::rule< ScannerT > multiplicativeExpression; + ::boost::spirit::classic::rule< ScannerT > unaryExpression; + ::boost::spirit::classic::rule< ScannerT > basicExpression; + ::boost::spirit::classic::rule< ScannerT > unaryFunction; + ::boost::spirit::classic::rule< ScannerT > binaryFunction; + ::boost::spirit::classic::rule< ScannerT > identifier; + }; + + const ParserContextSharedPtr& getContext() const + { + return mpParserContext; + } + + private: + ParserContextSharedPtr mpParserContext; // might get modified during parsing + }; + + const ParserContextSharedPtr& getParserContext() + { + static ParserContextSharedPtr lcl_parserContext = std::make_shared<ParserContext>(); + + // clear node stack (since we reuse the static object, that's + // the whole point here) + while( !lcl_parserContext->maOperandStack.empty() ) + lcl_parserContext->maOperandStack.pop(); + + return lcl_parserContext; + } + } + + std::shared_ptr<ExpressionNode> const & SmilFunctionParser::parseSmilValue( const OUString& rSmilValue, + const ::basegfx::B2DRectangle& rRelativeShapeBounds ) + { + // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* + // gives better conversion robustness here (we might want to map space + // etc. to ASCII space here) + const OString& rAsciiSmilValue( + OUStringToOString( rSmilValue, RTL_TEXTENCODING_ASCII_US ) ); + + StringIteratorT aStart( rAsciiSmilValue.getStr() ); + StringIteratorT aEnd( rAsciiSmilValue.getStr()+rAsciiSmilValue.getLength() ); + + // static parser context, because the actual + // Spirit parser is also a static object + ParserContextSharedPtr pContext = getParserContext(); + + pContext->maShapeBounds = rRelativeShapeBounds; + pContext->mbParseAnimationFunction = false; // parse with '$' disabled + + + ExpressionGrammar aExpressionGrammer( pContext ); + const ::boost::spirit::classic::parse_info<StringIteratorT> aParseInfo( + ::boost::spirit::classic::parse( aStart, + aEnd, + aExpressionGrammer, + ::boost::spirit::classic::space_p ) ); + +#if OSL_DEBUG_LEVEL > 0 + ::std::cout.flush(); // needed to keep stdout and cout in sync +#endif + + // input fully congested by the parser? + if( !aParseInfo.full ) + throw ParseError( "SmilFunctionParser::parseSmilValue(): string not fully parseable" ); + + // parser's state stack now must contain exactly _one_ ExpressionNode, + // which represents our formula. + if( pContext->maOperandStack.size() != 1 ) + throw ParseError( "SmilFunctionParser::parseSmilValue(): incomplete or empty expression" ); + + return pContext->maOperandStack.top(); + } + + std::shared_ptr<ExpressionNode> const & SmilFunctionParser::parseSmilFunction( const OUString& rSmilFunction, + const ::basegfx::B2DRectangle& rRelativeShapeBounds ) + { + // TODO(Q1): Check if a combination of the RTL_UNICODETOTEXT_FLAGS_* + // gives better conversion robustness here (we might want to map space + // etc. to ASCII space here) + const OString& rAsciiSmilFunction( + OUStringToOString( rSmilFunction, RTL_TEXTENCODING_ASCII_US ) ); + + StringIteratorT aStart( rAsciiSmilFunction.getStr() ); + StringIteratorT aEnd( rAsciiSmilFunction.getStr()+rAsciiSmilFunction.getLength() ); + + // static parser context, because the actual + // Spirit parser is also a static object + ParserContextSharedPtr pContext = getParserContext(); + + pContext->maShapeBounds = rRelativeShapeBounds; + pContext->mbParseAnimationFunction = true; // parse with '$' enabled + + + ExpressionGrammar aExpressionGrammer( pContext ); + const ::boost::spirit::classic::parse_info<StringIteratorT> aParseInfo( + ::boost::spirit::classic::parse( aStart, + aEnd, + aExpressionGrammer >> ::boost::spirit::classic::end_p, + ::boost::spirit::classic::space_p ) ); + +#if OSL_DEBUG_LEVEL > 0 + ::std::cout.flush(); // needed to keep stdout and cout in sync +#endif + // input fully congested by the parser? + if( !aParseInfo.full ) + throw ParseError( "SmilFunctionParser::parseSmilFunction(): string not fully parseable" ); + + // parser's state stack now must contain exactly _one_ ExpressionNode, + // which represents our formula. + if( pContext->maOperandStack.size() != 1 ) + throw ParseError( "SmilFunctionParser::parseSmilFunction(): incomplete or empty expression" ); + + return pContext->maOperandStack.top(); + } +} + +#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) + +// debug hooks + +namespace boost +{ + +void sp_scalar_constructor_hook(void *) +{ +} + +void sp_scalar_destructor_hook(void *) +{ +} + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/soundplayer.cxx b/slideshow/source/engine/soundplayer.cxx new file mode 100644 index 000000000..c0ae300c1 --- /dev/null +++ b/slideshow/source/engine/soundplayer.cxx @@ -0,0 +1,177 @@ +/* -*- 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 <config_features.h> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XComponent.hpp> + +#include <tools/urlobj.hxx> + +#include <avmedia/mediawindow.hxx> +#include <mediafilemanager.hxx> +#include <soundplayer.hxx> + +#include <algorithm> + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + // TODO(Q3): Move the whole SoundPlayer class to avmedia. + + std::shared_ptr<SoundPlayer> SoundPlayer::create( + EventMultiplexer & rEventMultiplexer, + const OUString& rSoundURL, + const uno::Reference< uno::XComponentContext>& rComponentContext, + MediaFileManager& rMediaFileManager) + { + std::shared_ptr<SoundPlayer> pPlayer( + new SoundPlayer( rEventMultiplexer, + rSoundURL, + rComponentContext, + rMediaFileManager) ); + rEventMultiplexer.addPauseHandler( pPlayer ); + pPlayer->mThis = pPlayer; + return pPlayer; + } + + bool SoundPlayer::handlePause( bool bPauseShow ) + { + return bPauseShow ? stopPlayback() : startPlayback(); + } + + void SoundPlayer::dispose() + { + if( mThis ) + { + mrEventMultiplexer.removePauseHandler( mThis ); + mThis.reset(); + } + + if( mxPlayer.is() ) + { + mxPlayer->stop(); + uno::Reference<lang::XComponent> xComponent( + mxPlayer, uno::UNO_QUERY ); + if( xComponent.is() ) + xComponent->dispose(); + mxPlayer.clear(); + } + } + + SoundPlayer::SoundPlayer( + EventMultiplexer & rEventMultiplexer, + const OUString& rSoundURL, + const uno::Reference< uno::XComponentContext>& rComponentContext, + MediaFileManager& rMediaFileManager) + : mrEventMultiplexer(rEventMultiplexer), + mThis(), + mxPlayer() + { + ENSURE_OR_THROW( rComponentContext.is(), + "SoundPlayer::SoundPlayer(): Invalid component context" ); + +#if !HAVE_FEATURE_AVMEDIA + (void) rMediaFileManager; +#else + try + { + if (rSoundURL.startsWithIgnoreAsciiCase("vnd.sun.star.Package:")) + { + mpMediaTempFile = rMediaFileManager.getMediaTempFile(rSoundURL); + } + const INetURLObject aURL( mpMediaTempFile ? mpMediaTempFile->m_TempFileURL : rSoundURL ); + mxPlayer = avmedia::MediaWindow::createPlayer( + aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ), ""/*TODO!*/ ); + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + } +#endif + + if( !mxPlayer.is() ) + throw lang::NoSupportException( + "No sound support for " + rSoundURL ); + } + + SoundPlayer::~SoundPlayer() + { + try + { + dispose(); + } + catch (uno::Exception &) { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + + double SoundPlayer::getDuration() const + { + if( !mxPlayer.is() ) + return 0.0; + + const double nDuration( mxPlayer->getDuration() ); + if( mxPlayer->isPlaying() ) + return ::std::max( 0.0, + nDuration - mxPlayer->getMediaTime() ); + else + return nDuration; + } + + bool SoundPlayer::startPlayback() + { + if( !mxPlayer.is() ) + return false; + + if( mxPlayer->isPlaying() ) + mxPlayer->stop(); + + mxPlayer->start(); + return true; + } + + bool SoundPlayer::stopPlayback() + { + if( mxPlayer.is() ) + mxPlayer->stop(); + + return true; + } + + void SoundPlayer::setPlaybackLoop( bool bLoop ) + { + if( mxPlayer.is() ) + mxPlayer->setPlaybackLoop( bLoop ); + } + + bool SoundPlayer::isPlaying() const + { + return mxPlayer->isPlaying(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/tools.cxx b/slideshow/source/engine/tools.cxx new file mode 100644 index 000000000..3de893ae9 --- /dev/null +++ b/slideshow/source/engine/tools.cxx @@ -0,0 +1,769 @@ +/* -*- 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 <canvas/canvastools.hxx> + +#include <math.h> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/animations/ValuePair.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/utils/lerp.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <cppcanvas/basegfxfactory.hxx> + +#include <unoview.hxx> +#include <slideshowexceptions.hxx> +#include <smilfunctionparser.hxx> +#include <tools.hxx> + +#include <limits> + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + namespace + { + class NamedValueComparator + { + public: + explicit NamedValueComparator( const beans::NamedValue& rKey ) : + mrKey( rKey ) + { + } + + bool operator()( const beans::NamedValue& rValue ) const + { + return rValue.Name == mrKey.Name && rValue.Value == mrKey.Value; + } + + private: + const beans::NamedValue& mrKey; + }; + + ::basegfx::B2DHomMatrix getAttributedShapeTransformation( const ::basegfx::B2DRectangle& rShapeBounds, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + ::basegfx::B2DHomMatrix aTransform; + const ::basegfx::B2DSize& rSize( rShapeBounds.getRange() ); + + const double nShearX( pAttr->isShearXAngleValid() ? + pAttr->getShearXAngle() : + 0.0 ); + const double nShearY( pAttr->isShearYAngleValid() ? + pAttr->getShearYAngle() : + 0.0 ); + const double nRotation( pAttr->isRotationAngleValid() ? + basegfx::deg2rad(pAttr->getRotationAngle()) : + 0.0 ); + + // scale, shear and rotation pivot point is the shape + // center - adapt origin accordingly + aTransform.translate( -0.5, -0.5 ); + + // ensure valid size (zero size will inevitably lead + // to a singular transformation matrix) + aTransform.scale( ::basegfx::pruneScaleValue( + rSize.getX() ), + ::basegfx::pruneScaleValue( + rSize.getY() ) ); + + const bool bNeedShearX( !::basegfx::fTools::equalZero(nShearX) ); + const bool bNeedShearY( !::basegfx::fTools::equalZero(nShearY) ); + const bool bNeedRotation( !::basegfx::fTools::equalZero(nRotation) ); + + if( bNeedRotation || bNeedShearX || bNeedShearY ) + { + if( bNeedShearX ) + aTransform.shearX( nShearX ); + + if( bNeedShearY ) + aTransform.shearY( nShearY ); + + if( bNeedRotation ) + aTransform.rotate( nRotation ); + } + + // move left, top corner back to position of the + // shape. Since we've already translated the + // center of the shape to the origin (the + // translate( -0.5, -0.5 ) above), translate to + // center of final shape position here. + aTransform.translate( rShapeBounds.getCenterX(), + rShapeBounds.getCenterY() ); + + return aTransform; + } + } + + // Value extraction from Any + // ========================= + + /// extract unary double value from Any + bool extractValue( double& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) + { + // try to extract numeric value (double, or smaller POD, like float or int) + if( rSourceAny >>= o_rValue) + { + // succeeded + return true; + } + + // try to extract string + OUString aString; + if( !(rSourceAny >>= aString) ) + return false; // nothing left to try + + // parse the string into an ExpressionNode + try + { + // Parse string into ExpressionNode, eval node at time 0.0 + o_rValue = (*SmilFunctionParser::parseSmilValue( + aString, + calcRelativeShapeBounds(rSlideBounds, + rShape->getBounds()) ))(0.0); + } + catch( ParseError& ) + { + return false; + } + + return true; + } + + /// extract enum/constant group value from Any + bool extractValue( sal_Int32& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& /*rShape*/, + const ::basegfx::B2DVector& /*rSlideBounds*/ ) + { + // try to extract numeric value (int, or smaller POD, like byte) + if( rSourceAny >>= o_rValue) + { + // succeeded + return true; + } + + // okay, no plain int. Maybe one of the domain-specific enums? + drawing::FillStyle eFillStyle; + if( rSourceAny >>= eFillStyle ) + { + o_rValue = sal::static_int_cast<sal_Int16>(eFillStyle); + + // succeeded + return true; + } + + drawing::LineStyle eLineStyle; + if( rSourceAny >>= eLineStyle ) + { + o_rValue = sal::static_int_cast<sal_Int16>(eLineStyle); + + // succeeded + return true; + } + + awt::FontSlant eFontSlant; + if( rSourceAny >>= eFontSlant ) + { + o_rValue = sal::static_int_cast<sal_Int16>(eFontSlant); + + // succeeded + return true; + } + + // nothing left to try. Failure + return false; + } + + /// extract enum/constant group value from Any + bool extractValue( sal_Int16& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) + { + sal_Int32 aValue; + if( !extractValue(aValue,rSourceAny,rShape,rSlideBounds) ) + return false; + + if( std::numeric_limits<sal_Int16>::max() < aValue || + std::numeric_limits<sal_Int16>::min() > aValue ) + { + return false; + } + + o_rValue = static_cast<sal_Int16>(aValue); + + return true; + } + + /// extract color value from Any + bool extractValue( RGBColor& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& /*rShape*/, + const ::basegfx::B2DVector& /*rSlideBounds*/ ) + { + // try to extract numeric value (double, or smaller POD, like float or int) + { + double nTmp = 0; + if( rSourceAny >>= nTmp ) + { + sal_uInt32 aIntColor( static_cast< sal_uInt32 >(nTmp) ); + + // TODO(F2): Handle color values correctly, here + o_rValue = unoColor2RGBColor( aIntColor ); + + // succeeded + return true; + } + } + + // try double sequence + { + uno::Sequence< double > aTmp; + if( rSourceAny >>= aTmp ) + { + ENSURE_OR_THROW( aTmp.getLength() == 3, + "extractValue(): inappropriate length for RGB color value" ); + + o_rValue = RGBColor( aTmp[0], aTmp[1], aTmp[2] ); + + // succeeded + return true; + } + } + + // try sal_Int32 sequence + { + uno::Sequence< sal_Int32 > aTmp; + if( rSourceAny >>= aTmp ) + { + ENSURE_OR_THROW( aTmp.getLength() == 3, + "extractValue(): inappropriate length for RGB color value" ); + + // truncate to byte + o_rValue = RGBColor( ::cppcanvas::makeColor( + static_cast<sal_uInt8>(aTmp[0]), + static_cast<sal_uInt8>(aTmp[1]), + static_cast<sal_uInt8>(aTmp[2]), + 255 ) ); + + // succeeded + return true; + } + } + + // try sal_Int8 sequence + { + uno::Sequence< sal_Int8 > aTmp; + if( rSourceAny >>= aTmp ) + { + ENSURE_OR_THROW( aTmp.getLength() == 3, + "extractValue(): inappropriate length for RGB color value" ); + + o_rValue = RGBColor( ::cppcanvas::makeColor( aTmp[0], aTmp[1], aTmp[2], 255 ) ); + + // succeeded + return true; + } + } + + // try to extract string + OUString aString; + if( !(rSourceAny >>= aString) ) + return false; // nothing left to try + + // TODO(F2): Provide symbolic color values here + o_rValue = RGBColor( 0.5, 0.5, 0.5 ); + + return true; + } + + /// extract color value from Any + bool extractValue( HSLColor& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& /*rShape*/, + const ::basegfx::B2DVector& /*rSlideBounds*/ ) + { + // try double sequence + { + uno::Sequence< double > aTmp; + if( rSourceAny >>= aTmp ) + { + ENSURE_OR_THROW( aTmp.getLength() == 3, + "extractValue(): inappropriate length for HSL color value" ); + + o_rValue = HSLColor( aTmp[0], aTmp[1], aTmp[2] ); + + // succeeded + return true; + } + } + + // try sal_Int8 sequence + { + uno::Sequence< sal_Int8 > aTmp; + if( rSourceAny >>= aTmp ) + { + ENSURE_OR_THROW( aTmp.getLength() == 3, + "extractValue(): inappropriate length for HSL color value" ); + + o_rValue = HSLColor( aTmp[0]*360.0/255.0, aTmp[1]/255.0, aTmp[2]/255.0 ); + + // succeeded + return true; + } + } + + return false; // nothing left to try + } + + /// extract plain string from Any + bool extractValue( OUString& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& /*rShape*/, + const ::basegfx::B2DVector& /*rSlideBounds*/ ) + { + // try to extract string + return rSourceAny >>= o_rValue; + } + + /// extract bool value from Any + bool extractValue( bool& o_rValue, + const uno::Any& rSourceAny, + const ShapeSharedPtr& /*rShape*/, + const ::basegfx::B2DVector& /*rSlideBounds*/ ) + { + bool bTmp; + // try to extract bool value + if( rSourceAny >>= bTmp ) + { + o_rValue = bTmp; + + // succeeded + return true; + } + + // try to extract string + OUString aString; + if( !(rSourceAny >>= aString) ) + return false; // nothing left to try + + // we also take the strings "true" and "false", + // as well as "on" and "off" here + if( aString.equalsIgnoreAsciiCase("true") || + aString.equalsIgnoreAsciiCase("on") ) + { + o_rValue = true; + return true; + } + if( aString.equalsIgnoreAsciiCase("false") || + aString.equalsIgnoreAsciiCase("off") ) + { + o_rValue = false; + return true; + } + + // ultimately failed. + return false; + } + + /// extract double 2-tuple from Any + bool extractValue( ::basegfx::B2DTuple& o_rPair, + const uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) + { + animations::ValuePair aPair; + + if( !(rSourceAny >>= aPair) ) + return false; + + double nFirst; + if( !extractValue( nFirst, aPair.First, rShape, rSlideBounds ) ) + return false; + + double nSecond; + if( !extractValue( nSecond, aPair.Second, rShape, rSlideBounds ) ) + return false; + + o_rPair.setX( nFirst ); + o_rPair.setY( nSecond ); + + return true; + } + + bool findNamedValue( uno::Sequence< beans::NamedValue > const& rSequence, + const beans::NamedValue& rSearchKey ) + { + return ::std::any_of( rSequence.begin(), rSequence.end(), + NamedValueComparator( rSearchKey ) ); + } + + basegfx::B2DRange calcRelativeShapeBounds( const basegfx::B2DVector& rPageSize, + const basegfx::B2DRange& rShapeBounds ) + { + return basegfx::B2DRange( rShapeBounds.getMinX() / rPageSize.getX(), + rShapeBounds.getMinY() / rPageSize.getY(), + rShapeBounds.getMaxX() / rPageSize.getX(), + rShapeBounds.getMaxY() / rPageSize.getY() ); + } + + // TODO(F2): Currently, the positional attributes DO NOT mirror the XShape properties. + // First and foremost, this is because we must operate with the shape boundrect, + // not position and size (the conversion between logic rect, snap rect and boundrect + // are non-trivial for draw shapes, and I won't duplicate them here). Thus, shapes + // rotated on the page will still have 0.0 rotation angle, as the metafile + // representation fetched over the API is our default zero case. + + ::basegfx::B2DHomMatrix getShapeTransformation( const ::basegfx::B2DRectangle& rShapeBounds, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + if( !pAttr ) + { + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + rShapeBounds.getWidth(), rShapeBounds.getHeight(), + rShapeBounds.getMinX(), rShapeBounds.getMinY())); + + return aTransform; + } + else + { + return getAttributedShapeTransformation( rShapeBounds, + pAttr ); + } + } + + ::basegfx::B2DHomMatrix getSpriteTransformation( const ::basegfx::B2DVector& rPixelSize, + const ::basegfx::B2DVector& rOrigSize, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + ::basegfx::B2DHomMatrix aTransform; + + if( pAttr ) + { + const double nShearX( pAttr->isShearXAngleValid() ? + pAttr->getShearXAngle() : + 0.0 ); + const double nShearY( pAttr->isShearYAngleValid() ? + pAttr->getShearYAngle() : + 0.0 ); + const double nRotation( pAttr->isRotationAngleValid() ? + basegfx::deg2rad(pAttr->getRotationAngle()) : + 0.0 ); + + // scale, shear and rotation pivot point is the + // sprite's pixel center - adapt origin accordingly + aTransform.translate( -0.5*rPixelSize.getX(), + -0.5*rPixelSize.getY() ); + + const ::basegfx::B2DSize aSize( + pAttr->isWidthValid() ? pAttr->getWidth() : rOrigSize.getX(), + pAttr->isHeightValid() ? pAttr->getHeight() : rOrigSize.getY() ); + + // ensure valid size (zero size will inevitably lead + // to a singular transformation matrix). + aTransform.scale( ::basegfx::pruneScaleValue( + aSize.getX() / + ::basegfx::pruneScaleValue( + rOrigSize.getX() ) ), + ::basegfx::pruneScaleValue( + aSize.getY() / + ::basegfx::pruneScaleValue( + rOrigSize.getY() ) ) ); + + const bool bNeedShearX( !::basegfx::fTools::equalZero(nShearX) ); + const bool bNeedShearY( !::basegfx::fTools::equalZero(nShearY) ); + const bool bNeedRotation( !::basegfx::fTools::equalZero(nRotation) ); + + if( bNeedRotation || bNeedShearX || bNeedShearY ) + { + if( bNeedShearX ) + aTransform.shearX( nShearX ); + + if( bNeedShearY ) + aTransform.shearY( nShearY ); + + if( bNeedRotation ) + aTransform.rotate( nRotation ); + } + + // move left, top corner back to original position of + // the sprite (we've translated the center of the + // sprite to the origin above). + aTransform.translate( 0.5*rPixelSize.getX(), + 0.5*rPixelSize.getY() ); + } + + // return identity transform for un-attributed + // shapes. This renders the sprite as-is, in its + // document-supplied size. + return aTransform; + } + + ::basegfx::B2DRectangle getShapeUpdateArea( const ::basegfx::B2DRectangle& rUnitBounds, + const ::basegfx::B2DHomMatrix& rShapeTransform, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + ::basegfx::B2DHomMatrix aTransform; + + if( pAttr && + pAttr->isCharScaleValid() && + fabs(pAttr->getCharScale()) > 1.0 ) + { + // enlarge shape bounds. Have to consider the worst + // case here (the text fully fills the shape) + + const double nCharScale( pAttr->getCharScale() ); + + // center of scaling is the middle of the shape + aTransform.translate( -0.5, -0.5 ); + aTransform.scale( nCharScale, nCharScale ); + aTransform.translate( 0.5, 0.5 ); + } + + aTransform *= rShapeTransform; + + ::basegfx::B2DRectangle aRes; + + // apply shape transformation to unit rect + return ::canvas::tools::calcTransformedRectBounds( + aRes, + rUnitBounds, + aTransform ); + } + + ::basegfx::B2DRange getShapeUpdateArea( const ::basegfx::B2DRange& rUnitBounds, + const ::basegfx::B2DRange& rShapeBounds ) + { + return ::basegfx::B2DRectangle( + basegfx::utils::lerp( rShapeBounds.getMinX(), + rShapeBounds.getMaxX(), + rUnitBounds.getMinX() ), + basegfx::utils::lerp( rShapeBounds.getMinY(), + rShapeBounds.getMaxY(), + rUnitBounds.getMinY() ), + basegfx::utils::lerp( rShapeBounds.getMinX(), + rShapeBounds.getMaxX(), + rUnitBounds.getMaxX() ), + basegfx::utils::lerp( rShapeBounds.getMinY(), + rShapeBounds.getMaxY(), + rUnitBounds.getMaxY() ) ); + } + + ::basegfx::B2DRectangle getShapePosSize( const ::basegfx::B2DRectangle& rOrigBounds, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + // an already empty shape bound need no further + // treatment. In fact, any changes applied below would + // actually remove the special empty state, thus, don't + // change! + if( !pAttr || + rOrigBounds.isEmpty() ) + { + return rOrigBounds; + } + else + { + // cannot use maBounds anymore, attributes might have been + // changed by now. + // Have to use absolute values here, as negative sizes + // (aka mirrored shapes) _still_ have the same bounds, + // only with mirrored content. + ::basegfx::B2DSize aSize; + aSize.setX( fabs( pAttr->isWidthValid() ? + pAttr->getWidth() : + rOrigBounds.getWidth() ) ); + aSize.setY( fabs( pAttr->isHeightValid() ? + pAttr->getHeight() : + rOrigBounds.getHeight() ) ); + + ::basegfx::B2DPoint aPos; + aPos.setX( pAttr->isPosXValid() ? + pAttr->getPosX() : + rOrigBounds.getCenterX() ); + aPos.setY( pAttr->isPosYValid() ? + pAttr->getPosY() : + rOrigBounds.getCenterY() ); + + // the positional attribute retrieved from the + // ShapeAttributeLayer actually denotes the _middle_ + // of the shape (do it as the PPTs do...) + return ::basegfx::B2DRectangle( aPos - 0.5*aSize, + aPos + 0.5*aSize ); + } + } + + RGBColor unoColor2RGBColor( sal_Int32 nColor ) + { + return RGBColor( + ::cppcanvas::makeColor( + // convert from API color to IntSRGBA color + // (0xAARRGGBB -> 0xRRGGBBAA) + static_cast< sal_uInt8 >( nColor >> 16U ), + static_cast< sal_uInt8 >( nColor >> 8U ), + static_cast< sal_uInt8 >( nColor ), + static_cast< sal_uInt8 >( nColor >> 24U ) ) ); + } + + sal_Int32 RGBAColor2UnoColor( ::cppcanvas::IntSRGBA aColor ) + { + return ::cppcanvas::makeColorARGB( + // convert from IntSRGBA color to API color + // (0xRRGGBBAA -> 0xAARRGGBB) + static_cast< sal_uInt8 >(0), + ::cppcanvas::getRed(aColor), + ::cppcanvas::getGreen(aColor), + ::cppcanvas::getBlue(aColor)); + } + + void fillRect( const ::cppcanvas::CanvasSharedPtr& rCanvas, + const ::basegfx::B2DRectangle& rRect, + ::cppcanvas::IntSRGBA aFillColor ) + { + const ::basegfx::B2DPolygon aPoly( + ::basegfx::utils::createPolygonFromRect( rRect )); + + ::cppcanvas::PolyPolygonSharedPtr pPolyPoly( + ::cppcanvas::BaseGfxFactory::createPolyPolygon( rCanvas, aPoly ) ); + + if( pPolyPoly ) + { + pPolyPoly->setRGBAFillColor( aFillColor ); + pPolyPoly->draw(); + } + } + + void initSlideBackground( const ::cppcanvas::CanvasSharedPtr& rCanvas, + const ::basegfx::B2ISize& rSize ) + { + ::cppcanvas::CanvasSharedPtr pCanvas( rCanvas->clone() ); + + // set transformation to identity (->device pixel) + pCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // #i42440# Fill the _full_ background in + // black. Since we had to extend the bitmap by one + // pixel, and the bitmap is initialized white, + // depending on the slide content a one pixel wide + // line will show to the bottom and the right. + fillRect( pCanvas, + ::basegfx::B2DRectangle( 0.0, 0.0, + rSize.getX(), + rSize.getY() ), + 0x000000FFU ); + + // fill the bounds rectangle in white. Subtract one pixel + // from both width and height, because the slide size is + // chosen one pixel larger than given by the drawing + // layer. This is because shapes with line style, that + // have the size of the slide would otherwise be cut + // off. OTOH, every other slide background (solid fill, + // gradient, bitmap) render one pixel less, thus revealing + // ugly white pixel to the right and the bottom. + fillRect( pCanvas, + ::basegfx::B2DRectangle( 0.0, 0.0, + rSize.getX()-1, + rSize.getY()-1 ), + 0xFFFFFFFFU ); + } + + ::basegfx::B2DRectangle getAPIShapeBounds( const uno::Reference< drawing::XShape >& xShape ) + { + uno::Reference< beans::XPropertySet > xPropSet( xShape, + uno::UNO_QUERY_THROW ); + // read bound rect + awt::Rectangle aTmpRect; + if( !(xPropSet->getPropertyValue("BoundRect") >>= aTmpRect) ) + { + ENSURE_OR_THROW( false, + "getAPIShapeBounds(): Could not get \"BoundRect\" property from shape" ); + } + + return ::basegfx::B2DRectangle( aTmpRect.X, + aTmpRect.Y, + aTmpRect.X+aTmpRect.Width, + aTmpRect.Y+aTmpRect.Height ); + } + +/* + TODO(F1): When ZOrder someday becomes usable enable this + + double getAPIShapePrio( const uno::Reference< drawing::XShape >& xShape ) + { + uno::Reference< beans::XPropertySet > xPropSet( xShape, + uno::UNO_QUERY_THROW ); + // read prio + sal_Int32 nPrio(0); + if( !(xPropSet->getPropertyValue( + OUString("ZOrder") ) >>= nPrio) ) + { + ENSURE_OR_THROW( false, + "getAPIShapePrio(): Could not get \"ZOrder\" property from shape" ); + } + + // TODO(F2): Check and adapt the range of possible values here. + // Maybe we can also take the total number of shapes here + return nPrio / 65535.0; + } +*/ + + basegfx::B2IVector getSlideSizePixel( const basegfx::B2DVector& rSlideSize, + const UnoViewSharedPtr& pView ) + { + ENSURE_OR_THROW(pView, "getSlideSizePixel(): invalid view"); + + // determine transformed page bounds + const basegfx::B2DRange aRect( 0,0, + rSlideSize.getX(), + rSlideSize.getY() ); + basegfx::B2DRange aTmpRect; + canvas::tools::calcTransformedRectBounds( aTmpRect, + aRect, + pView->getTransformation() ); + + // #i42440# Returned slide size is one pixel too small, as + // rendering happens one pixel to the right and below the + // actual bound rect. + return basegfx::B2IVector( + basegfx::fround( aTmpRect.getRange().getX() ) + 1, + basegfx::fround( aTmpRect.getRange().getY() ) + 1 ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/barndoorwipe.cxx b/slideshow/source/engine/transitions/barndoorwipe.cxx new file mode 100644 index 000000000..c2621f931 --- /dev/null +++ b/slideshow/source/engine/transitions/barndoorwipe.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "barndoorwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon BarnDoorWipe::operator () ( double t ) +{ + if (m_doubled) + t /= 2.0; + + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5)); + aTransform.scale( ::basegfx::pruneScaleValue(t), 1.0 ); + aTransform.translate( 0.5, 0.5 ); + ::basegfx::B2DPolygon poly( m_unitRect ); + poly.transform( aTransform ); + ::basegfx::B2DPolyPolygon res(poly); + + if (m_doubled) { + aTransform = basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5); + aTransform.rotate( M_PI_2 ); + aTransform.translate( 0.5, 0.5 ); + poly.transform( aTransform ); + res.append(poly); + } + + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/barndoorwipe.hxx b/slideshow/source/engine/transitions/barndoorwipe.hxx new file mode 100644 index 000000000..775c54b6c --- /dev/null +++ b/slideshow/source/engine/transitions/barndoorwipe.hxx @@ -0,0 +1,49 @@ +/* -*- 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_TRANSITIONS_BARNDOORWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BARNDOORWIPE_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> + +#include "parametricpolypolygon.hxx" +#include "transitiontools.hxx" + +namespace slideshow::internal +{ +/// Generate a barn door wipe or double barn door wipe: +class BarnDoorWipe : public ParametricPolyPolygon +{ +public: + explicit BarnDoorWipe(bool doubled = false) + : m_unitRect(createUnitRect()) + , m_doubled(doubled) + { + } + virtual ::basegfx::B2DPolyPolygon operator()(double x) override; + +private: + const ::basegfx::B2DPolygon m_unitRect; + const bool m_doubled; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BARNDOORWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/barwipepolypolygon.cxx b/slideshow/source/engine/transitions/barwipepolypolygon.cxx new file mode 100644 index 000000000..d2f28df7d --- /dev/null +++ b/slideshow/source/engine/transitions/barwipepolypolygon.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 <basegfx/matrix/b2dhommatrix.hxx> +#include "barwipepolypolygon.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon BarWipePolyPolygon::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res; + ::basegfx::B2DHomMatrix aTransform; + aTransform.scale( ::basegfx::pruneScaleValue( t / m_nBars ), 1.0 ); + for ( sal_Int32 i = m_nBars; i--; ) + { + ::basegfx::B2DHomMatrix transform( aTransform ); + transform.translate( static_cast<double>(i) / m_nBars, 0.0 ); + ::basegfx::B2DPolygon poly( m_unitRect ); + poly.transform( transform ); + res.append( poly ); + } + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/barwipepolypolygon.hxx b/slideshow/source/engine/transitions/barwipepolypolygon.hxx new file mode 100644 index 000000000..610e50857 --- /dev/null +++ b/slideshow/source/engine/transitions/barwipepolypolygon.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_TRANSITIONS_BARWIPEPOLYPOLYGON_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BARWIPEPOLYPOLYGON_HXX + +#include "parametricpolypolygon.hxx" +#include "transitiontools.hxx" + + +namespace slideshow::internal { + +/// Generates a horizontal, left-to-right bar wipe: +class BarWipePolyPolygon : public ParametricPolyPolygon +{ +public: + explicit BarWipePolyPolygon( sal_Int32 nBars = 1 /* nBars > 1: blinds effect */ ) + : m_nBars(nBars), + m_unitRect( createUnitRect() ) + {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const sal_Int32 m_nBars; + const ::basegfx::B2DPolygon m_unitRect; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BARWIPEPOLYPOLYGON_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/boxwipe.cxx b/slideshow/source/engine/transitions/boxwipe.cxx new file mode 100644 index 000000000..0844a905c --- /dev/null +++ b/slideshow/source/engine/transitions/boxwipe.cxx @@ -0,0 +1,46 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include "boxwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon BoxWipe::operator () ( double t ) +{ + ::basegfx::B2DHomMatrix aTransform; + double d = ::basegfx::pruneScaleValue(t); + if (m_topCentered) { + aTransform.translate( -0.5, 0.0 ); + aTransform.scale( d, d ); + aTransform.translate( 0.5, 0.0 ); + } else { + aTransform.scale( d, d ); + } + + ::basegfx::B2DPolyPolygon res( m_unitRect ); + res.transform( aTransform ); + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/boxwipe.hxx b/slideshow/source/engine/transitions/boxwipe.hxx new file mode 100644 index 000000000..d542e3e56 --- /dev/null +++ b/slideshow/source/engine/transitions/boxwipe.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_TRANSITIONS_BOXWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BOXWIPE_HXX + +#include "parametricpolypolygon.hxx" +#include "transitiontools.hxx" + + +namespace slideshow::internal { + +/// Generates a default topleft to right bottom box wipe +class BoxWipe : public ParametricPolyPolygon +{ +public: + explicit BoxWipe( bool topCentered ) : m_topCentered(topCentered), + m_unitRect( createUnitRect() ) + {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const bool m_topCentered; + const ::basegfx::B2DPolyPolygon m_unitRect; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_BOXWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/checkerboardwipe.cxx b/slideshow/source/engine/transitions/checkerboardwipe.cxx new file mode 100644 index 000000000..a88224a33 --- /dev/null +++ b/slideshow/source/engine/transitions/checkerboardwipe.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include "checkerboardwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon CheckerBoardWipe::operator () ( double t ) +{ + const double d = 1.0 / m_unitsPerEdge; + ::basegfx::B2DHomMatrix aTransform; + aTransform.scale( ::basegfx::pruneScaleValue( d * 2.0 * t ), + ::basegfx::pruneScaleValue( d ) ); + + ::basegfx::B2DPolyPolygon res; + for ( sal_Int32 i = m_unitsPerEdge; i--; ) + { + ::basegfx::B2DHomMatrix transform( aTransform ); + if ((i % 2) == 1) // odd line + transform.translate( -d, 0.0 ); + for ( sal_Int32 j = (m_unitsPerEdge / 2) + 1; j--; ) + { + ::basegfx::B2DPolyPolygon poly( m_unitRect ); + poly.transform( transform ); + res.append( poly ); + transform.translate( d * 2.0, 0.0 ); + } + aTransform.translate( 0.0, d ); // next line + } + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/checkerboardwipe.hxx b/slideshow/source/engine/transitions/checkerboardwipe.hxx new file mode 100644 index 000000000..e3d70f68d --- /dev/null +++ b/slideshow/source/engine/transitions/checkerboardwipe.hxx @@ -0,0 +1,50 @@ +/* -*- 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_TRANSITIONS_CHECKERBOARDWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CHECKERBOARDWIPE_HXX + +#include <osl/diagnose.h> + +#include "transitiontools.hxx" +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generate a check board wipe (across) +class CheckerBoardWipe : public ParametricPolyPolygon +{ +public: + explicit CheckerBoardWipe( sal_Int32 unitsPerEdge = 10 ) + : m_unitsPerEdge(unitsPerEdge), + m_unitRect( createUnitRect() ) + { OSL_ASSERT( (unitsPerEdge % 2) == 0 ); } + virtual ::basegfx::B2DPolyPolygon operator () ( double x ) override; +private: + const sal_Int32 m_unitsPerEdge; + const ::basegfx::B2DPolyPolygon m_unitRect; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CHECKERBOARDWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/clippingfunctor.cxx b/slideshow/source/engine/transitions/clippingfunctor.cxx new file mode 100644 index 000000000..be592ac49 --- /dev/null +++ b/slideshow/source/engine/transitions/clippingfunctor.cxx @@ -0,0 +1,209 @@ +/* -*- 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 "clippingfunctor.hxx" + +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +namespace slideshow::internal +{ + ClippingFunctor::ClippingFunctor(const ParametricPolyPolygonSharedPtr& rPolygon, + const TransitionInfo& rTransitionInfo, + bool bDirectionForward, + bool bModeIn ) : + mpParametricPoly( rPolygon ), + maStaticTransformation(), + mbForwardParameterSweep( true ), + mbSubtractPolygon( false ), + mbScaleIsotrophically( rTransitionInfo.mbScaleIsotrophically ), + mbFlip(false) + { + ENSURE_OR_THROW( rPolygon, + "ClippingFunctor::ClippingFunctor(): Invalid parametric polygon" ); + + // maBackgroundRect serves as the minuent when + // subtracting a given clip polygon from the + // background. To speed up the clipper algo, avoid + // actual intersections of the generated + // poly-polygon with the minuent - i.e. choose the + // polygon to subtract from sufficiently large. + + // blow up unit rect to (-1,-1),(2,2) + // AW: Not needed, just use range + // ::basegfx::B2DHomMatrix aMatrix; + // aMatrix.scale(3.0,3.0); + // aMatrix.translate(-1.0,-1.0); + // maBackgroundRect.transform( aMatrix ); + + // extract modification info from maTransitionInfo + + + // perform general transformations _before_ the reverse + // mode changes. This allows the Transition table to be + // filled more consistently (otherwise, when e.g. rotating + // a clip 90 degrees, the ReverseMethod::FlipX becomes + // ReverseMethod::FlipY instead) + if (rTransitionInfo.mnRotationAngle != 0.0 || + rTransitionInfo.mnScaleX != 1.0 || + rTransitionInfo.mnScaleY != 1.0) + { + maStaticTransformation.translate( -0.5, -0.5 ); + // apply further transformations: + if (rTransitionInfo.mnRotationAngle != 0.0) + { + maStaticTransformation.rotate( + basegfx::deg2rad(rTransitionInfo.mnRotationAngle) ); + } + if (rTransitionInfo.mnScaleX != 1.0 || + rTransitionInfo.mnScaleY != 1.0) + { + maStaticTransformation.scale( + rTransitionInfo.mnScaleX, + rTransitionInfo.mnScaleY ); + } + maStaticTransformation.translate( 0.5, 0.5 ); + } + + if( !bDirectionForward ) + { + // Client has requested reversed + // direction. Apply TransitionInfo's choice + // for that + switch( rTransitionInfo.meReverseMethod ) + { + default: + ENSURE_OR_THROW( + false, + "TransitionFactory::TransitionFactory(): Unexpected reverse method" ); + break; + + case TransitionInfo::ReverseMethod::Ignore: + break; + + case TransitionInfo::ReverseMethod::SubtractAndInvert: + mbForwardParameterSweep = !mbForwardParameterSweep; + mbSubtractPolygon = !mbSubtractPolygon; + break; + + case TransitionInfo::ReverseMethod::Rotate180: + maStaticTransformation = basegfx::utils::createRotateAroundPoint(0.5, 0.5, M_PI) + * maStaticTransformation; + break; + + case TransitionInfo::ReverseMethod::FlipX: + maStaticTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix(-1.0, 1.0, 1.0, 0.0) + * maStaticTransformation; + mbFlip = true; + break; + + case TransitionInfo::ReverseMethod::FlipY: + maStaticTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix(1.0, -1.0, 0.0, 1.0) + * maStaticTransformation; + mbFlip = true; + break; + } + } + + if( !bModeIn ) + { + // client has requested 'out' mode. Apply + // TransitionInfo's method of choice + if( rTransitionInfo.mbOutInvertsSweep ) + mbForwardParameterSweep = !mbForwardParameterSweep; + else + mbSubtractPolygon = !mbSubtractPolygon; + } + } + + ::basegfx::B2DPolyPolygon ClippingFunctor::operator()( double nValue, + const ::basegfx::B2DSize& rTargetSize ) + { + // modify clip polygon according to static + // transformation plus current shape size + ::basegfx::B2DHomMatrix aMatrix( maStaticTransformation ); + + // retrieve current clip polygon + ::basegfx::B2DPolyPolygon aClipPoly = (*mpParametricPoly)( + mbForwardParameterSweep ? nValue : 1.0 - nValue ); + + // TODO(Q4): workaround here, better be fixed in cppcanvas + if (aClipPoly.count() == 0) + aClipPoly.append( basegfx::B2DPolygon() ); + + if (mbFlip) + aClipPoly.flip(); + + if( mbSubtractPolygon ) + { + // subtract given polygon from background + // rect. Do that before any transformations. + + // calc maBackgroundRect \ aClipPoly + // ================================= + + // AW: Simplified + // use a range with fixed size (-1,-1),(2,2) + const basegfx::B2DRange aBackgroundRange(-1, -1, 2, 2); + const basegfx::B2DRange aClipPolyRange(aClipPoly.getB2DRange()); + + if(aBackgroundRange.isInside(aClipPolyRange)) + { + // combine polygons; make the clip polygon the hole + aClipPoly = ::basegfx::utils::correctOrientations(aClipPoly); + aClipPoly.flip(); + aClipPoly.insert(0, basegfx::utils::createPolygonFromRect(aBackgroundRange)); + } + else + { + // when not completely inside aBackgroundRange clipping is needed + // subtract aClipPoly from aBackgroundRange + const basegfx::B2DPolyPolygon aBackgroundPolyPoly(basegfx::utils::createPolygonFromRect(aBackgroundRange)); + aClipPoly = basegfx::utils::solvePolygonOperationDiff(aBackgroundPolyPoly, aClipPoly); + } + } + + // scale polygon up to current shape size + if( mbScaleIsotrophically ) + { + const double nScale( ::std::max( rTargetSize.getX(), + rTargetSize.getY() ) ); + aMatrix.scale( nScale, nScale ); + aMatrix.translate( -(nScale-rTargetSize.getX())/2.0, + -(nScale-rTargetSize.getY())/2.0 ); + } + else + { + aMatrix.scale( rTargetSize.getX(), + rTargetSize.getY() ); + } + + // apply cumulative transformation to clip polygon + aClipPoly.transform( aMatrix ); + + return aClipPoly; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/clippingfunctor.hxx b/slideshow/source/engine/transitions/clippingfunctor.hxx new file mode 100644 index 000000000..666aef63b --- /dev/null +++ b/slideshow/source/engine/transitions/clippingfunctor.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CLIPPINGFUNCTOR_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CLIPPINGFUNCTOR_HXX + +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <transitioninfo.hxx> +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal + { + /** Generates the final clipping polygon. + + This class serves as the functor, which generates the + final clipping polygon from a given ParametricPolyPolygon + and a TransitionInfo. + + The ParametricPolyPolygon can be obtained from the + ParametricPolyPolygonFactory, see there. + + The TransitionInfo further parametrizes the polygon + generated by the ParametricPolyPolygon, with common + modifications such as rotation, flipping, or change of + direction. This allows the ParametricPolyPolygonFactory to + provide only prototypical shapes, with the ClippingFunctor + further customizing the output. + */ + class ClippingFunctor + { + public: + ClippingFunctor( + const ParametricPolyPolygonSharedPtr& rPolygon, + const TransitionInfo& rTransitionInfo, + bool bDirectionForward, + bool bModeIn ); + + /** Generate clip polygon. + + @param nValue + Value to generate the polygon for. Must be in the + range [0,1]. + + @param rTargetSize + Size the clip polygon should cover. This is typically + the size of the object the effect is applied on. + */ + ::basegfx::B2DPolyPolygon operator()( double nValue, + const ::basegfx::B2DSize& rTargetSize ); + + private: + ParametricPolyPolygonSharedPtr mpParametricPoly; + ::basegfx::B2DHomMatrix maStaticTransformation; + // AW: Not needed + // ::basegfx::B2DPolyPolygon maBackgroundRect; + bool mbForwardParameterSweep; + bool mbSubtractPolygon; + const bool mbScaleIsotrophically; + bool mbFlip; + }; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CLIPPINGFUNCTOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/clockwipe.cxx b/slideshow/source/engine/transitions/clockwipe.cxx new file mode 100644 index 000000000..bba29bfca --- /dev/null +++ b/slideshow/source/engine/transitions/clockwipe.cxx @@ -0,0 +1,62 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "clockwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolygon ClockWipe::calcCenteredClock( double t, double e ) +{ + ::basegfx::B2DPolygon poly; + ::basegfx::B2DHomMatrix aTransform; + aTransform.rotate( t * 2.0 * M_PI ); + const double MAX_EDGE = 2.0; + ::basegfx::B2DPoint p( 0.0, -MAX_EDGE ); + p *= aTransform; + poly.append( p ); + if (t >= 0.875) + poly.append( ::basegfx::B2DPoint( -e, -e ) ); + if (t >= 0.625) + poly.append( ::basegfx::B2DPoint( -e, e ) ); + if (t >= 0.375) + poly.append( ::basegfx::B2DPoint( e, e ) ); + if (t >= 0.125) + poly.append( ::basegfx::B2DPoint( e, -e ) ); + poly.append( ::basegfx::B2DPoint( 0.0, -e ) ); + poly.append( ::basegfx::B2DPoint( 0.0, 0.0 ) ); + poly.setClosed(true); + return poly; +} + +::basegfx::B2DPolyPolygon ClockWipe::operator () ( double t ) +{ + const basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(0.5, 0.5, 0.5, 0.5)); + ::basegfx::B2DPolygon poly( calcCenteredClock(t) ); + poly.transform( aTransform ); + return ::basegfx::B2DPolyPolygon(poly); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/clockwipe.hxx b/slideshow/source/engine/transitions/clockwipe.hxx new file mode 100644 index 000000000..b6745ed36 --- /dev/null +++ b/slideshow/source/engine/transitions/clockwipe.hxx @@ -0,0 +1,40 @@ +/* -*- 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_TRANSITIONS_CLOCKWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CLOCKWIPE_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> +#include "parametricpolypolygon.hxx" + +namespace slideshow::internal +{ +/// Generates a clockWiseTwelve clock wipe: +class ClockWipe : public ParametricPolyPolygon +{ +public: + /// 0,1 to 1,1 to 1,0 to 0,-1 to -1,0 to 0,1: + static ::basegfx::B2DPolygon calcCenteredClock(double t, double e = 1.0); + virtual ::basegfx::B2DPolyPolygon operator()(double t) override; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_CLOCKWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/combtransition.cxx b/slideshow/source/engine/transitions/combtransition.cxx new file mode 100644 index 000000000..3fcdbb2e0 --- /dev/null +++ b/slideshow/source/engine/transitions/combtransition.cxx @@ -0,0 +1,178 @@ +/* -*- 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 <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include "combtransition.hxx" + +namespace slideshow::internal { + +namespace { + +basegfx::B2DPolyPolygon createClipPolygon( + const ::basegfx::B2DVector& rDirection, + const ::basegfx::B2DSize& rSlideSize, + int nNumStrips, int nOffset ) +{ + // create clip polygon in standard orientation (will later + // be rotated to match direction vector) + ::basegfx::B2DPolyPolygon aClipPoly; + + // create nNumStrips/2 vertical strips + for( int i=nOffset; i<nNumStrips; i+=2 ) + { + aClipPoly.append( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( double(i)/nNumStrips, 0.0, + double(i+1)/nNumStrips, 1.0) ) ); + + } + + // rotate polygons, such that the strips are parallel to + // the given direction vector + const ::basegfx::B2DVector aUpVec(0.0, 1.0); + basegfx::B2DHomMatrix aMatrix(basegfx::utils::createRotateAroundPoint(0.5, 0.5, aUpVec.angle( rDirection ))); + + // blow up clip polygon to slide size + aMatrix.scale( rSlideSize.getX(), + rSlideSize.getY() ); + + aClipPoly.transform( aMatrix ); + + return aClipPoly; +} + +} + +CombTransition::CombTransition( + std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const ::basegfx::B2DVector& rPushDirection, + sal_Int32 nNumStripes ) + : SlideChangeBase( leavingSlide, pEnteringSlide, pSoundPlayer, + rViewContainer, rScreenUpdater, rEventMultiplexer, + false /* no leaving sprite */, + false /* no entering sprite */ ), + maPushDirectionUnit( rPushDirection ), + mnNumStripes( nNumStripes ) +{ +} + +void CombTransition::renderComb( double t, + const ViewEntry& rViewEntry ) const +{ + const SlideBitmapSharedPtr& pEnteringBitmap = getEnteringBitmap(rViewEntry); + const cppcanvas::CanvasSharedPtr pCanvas_ = rViewEntry.mpView->getCanvas(); + + if( !pEnteringBitmap || !pCanvas_ ) + return; + + // calc bitmap offsets. The enter/leaving bitmaps are only + // as large as the actual slides. For scaled-down + // presentations, we have to move the left, top edge of + // those bitmaps to the actual position, governed by the + // given view transform. The aBitmapPosPixel local + // variable is already in device coordinate space + // (i.e. pixel). + + // TODO(F2): Properly respect clip here. Might have to be transformed, too. + const basegfx::B2DHomMatrix viewTransform( rViewEntry.mpView->getTransformation() ); + const basegfx::B2DPoint pageOrigin( viewTransform * basegfx::B2DPoint() ); + + // change transformation on cloned canvas to be in + // device pixel + cppcanvas::CanvasSharedPtr pCanvas( pCanvas_->clone() ); + basegfx::B2DPoint p; + + // TODO(Q2): Use basegfx bitmaps here + // TODO(F1): SlideBitmap is not fully portable between different canvases! + + const basegfx::B2DSize enteringSizePixel( + getEnteringSlideSizePixel( rViewEntry.mpView) ); + + const basegfx::B2DVector aPushDirection( + enteringSizePixel * maPushDirectionUnit ); + const basegfx::B2DPolyPolygon aClipPolygon1 = + createClipPolygon( maPushDirectionUnit, + enteringSizePixel, + mnNumStripes, 0 ); + const basegfx::B2DPolyPolygon aClipPolygon2 = + createClipPolygon( maPushDirectionUnit, + enteringSizePixel, + mnNumStripes, 1 ); + + SlideBitmapSharedPtr const & pLeavingBitmap = getLeavingBitmap(rViewEntry); + if( pLeavingBitmap ) + { + // render odd strips: + pLeavingBitmap->clip( aClipPolygon1 ); + // don't modify bitmap object (no move!): + p = basegfx::B2DPoint( pageOrigin + (t * aPushDirection) ); + pCanvas->setTransformation(basegfx::utils::createTranslateB2DHomMatrix(p.getX(), p.getY())); + pLeavingBitmap->draw( pCanvas ); + + // render even strips: + pLeavingBitmap->clip( aClipPolygon2 ); + // don't modify bitmap object (no move!): + p = basegfx::B2DPoint( pageOrigin - (t * aPushDirection) ); + pCanvas->setTransformation(basegfx::utils::createTranslateB2DHomMatrix(p.getX(), p.getY())); + pLeavingBitmap->draw( pCanvas ); + } + + // TODO(Q2): Use basegfx bitmaps here + // TODO(F1): SlideBitmap is not fully portable between different canvases! + + // render odd strips: + pEnteringBitmap->clip( aClipPolygon1 ); + // don't modify bitmap object (no move!): + p = basegfx::B2DPoint( pageOrigin + ((t - 1.0) * aPushDirection) ); + pCanvas->setTransformation(basegfx::utils::createTranslateB2DHomMatrix(p.getX(), p.getY())); + pEnteringBitmap->draw( pCanvas ); + + // render even strips: + pEnteringBitmap->clip( aClipPolygon2 ); + // don't modify bitmap object (no move!): + p = basegfx::B2DPoint( pageOrigin + ((1.0 - t) * aPushDirection) ); + pCanvas->setTransformation(basegfx::utils::createTranslateB2DHomMatrix(p.getX(), p.getY())); + pEnteringBitmap->draw( pCanvas ); +} + +bool CombTransition::operator()( double t ) +{ + std::for_each( beginViews(), + endViews(), + [this, &t]( const ViewEntry& rViewEntry ) + { return this->renderComb( t, rViewEntry ); } ); + + getScreenUpdater().notifyUpdate(); + + return true; +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/combtransition.hxx b/slideshow/source/engine/transitions/combtransition.hxx new file mode 100644 index 000000000..c9648e59d --- /dev/null +++ b/slideshow/source/engine/transitions/combtransition.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_TRANSITIONS_COMBTRANSITION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_COMBTRANSITION_HXX + +#include "slidechangebase.hxx" + +namespace slideshow::internal { + +/** Comb transition class. + + This class provides a SlideChangeAnimation, showing a + comb-like effect (stripes of alternating push effects). +*/ +class CombTransition : public SlideChangeBase +{ +public: + /** Create the comb transition effect. + + @param nNumStripes + Number of comb-like stripes to show in this effect + */ + CombTransition( ::std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const ::basegfx::B2DVector& rPushDirection, + sal_Int32 nNumStripes ); + + // NumberAnimation + virtual bool operator()( double x ) override; + +private: + const ::basegfx::B2DVector maPushDirectionUnit; + sal_Int32 mnNumStripes; + + void renderComb( double t, const ViewEntry& rViewEntry ) const; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_COMBTRANSITION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/doublediamondwipe.cxx b/slideshow/source/engine/transitions/doublediamondwipe.cxx new file mode 100644 index 000000000..9c482000d --- /dev/null +++ b/slideshow/source/engine/transitions/doublediamondwipe.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <basegfx/point/b2dpoint.hxx> +#include "doublediamondwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon DoubleDiamondWipe::operator () ( double t ) +{ + // outer: + const double a = ::basegfx::pruneScaleValue( 0.25 + (t * 0.75) ); + ::basegfx::B2DPolygon poly; + poly.append( ::basegfx::B2DPoint( 0.5 + a, 0.5 ) ); + poly.append( ::basegfx::B2DPoint( 0.5, 0.5 - a ) ); + poly.append( ::basegfx::B2DPoint( 0.5 - a, 0.5 ) ); + poly.append( ::basegfx::B2DPoint( 0.5, 0.5 + a ) ); + poly.setClosed(true); + ::basegfx::B2DPolyPolygon res(poly); + + // inner (reverse order to clip): + const double b = ::basegfx::pruneScaleValue( (1.0 - t) * 0.25 ); + poly.clear(); + poly.append( ::basegfx::B2DPoint( 0.5 + b, 0.5 ) ); + poly.append( ::basegfx::B2DPoint( 0.5, 0.5 + b ) ); + poly.append( ::basegfx::B2DPoint( 0.5 - b, 0.5 ) ); + poly.append( ::basegfx::B2DPoint( 0.5, 0.5 - b ) ); + poly.setClosed(true); + res.append(poly); + + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/doublediamondwipe.hxx b/slideshow/source/engine/transitions/doublediamondwipe.hxx new file mode 100644 index 000000000..c77a783ef --- /dev/null +++ b/slideshow/source/engine/transitions/doublediamondwipe.hxx @@ -0,0 +1,40 @@ +/* -*- 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_TRANSITIONS_DOUBLEDIAMONDWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_DOUBLEDIAMONDWIPE_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include "parametricpolypolygon.hxx" + +namespace slideshow::internal +{ +/// Generates a double diamond wipe: +class DoubleDiamondWipe : public ParametricPolyPolygon +{ +public: + DoubleDiamondWipe() {} + virtual ::basegfx::B2DPolyPolygon operator()(double x) override; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_DOUBLEDIAMONDWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/ellipsewipe.cxx b/slideshow/source/engine/transitions/ellipsewipe.cxx new file mode 100644 index 000000000..7a5001efd --- /dev/null +++ b/slideshow/source/engine/transitions/ellipsewipe.cxx @@ -0,0 +1,51 @@ +/* -*- 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/TransitionSubType.hpp> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include "ellipsewipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon EllipseWipe::operator () ( double t ) +{ + ::basegfx::B2DPoint rCenter(0.5,0.5); + double fRadius = ::basegfx::pruneScaleValue( t * M_SQRT2 / 2.0 ); + + if( mnSubType == com::sun::star::animations::TransitionSubType::VERTICAL ) + { + // oval: + ::basegfx::B2DPolygon poly ( + ::basegfx::utils::createPolygonFromEllipse( rCenter, fRadius*2, fRadius ) ); //Horizontal Ellipse is rotated by 90 degrees + return ::basegfx::B2DPolyPolygon( poly ); + } + else + { + // circle: + ::basegfx::B2DPolygon poly( + ::basegfx::utils::createPolygonFromCircle( rCenter, fRadius ) ); + return ::basegfx::B2DPolyPolygon( poly ); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/ellipsewipe.hxx b/slideshow/source/engine/transitions/ellipsewipe.hxx new file mode 100644 index 000000000..41b488664 --- /dev/null +++ b/slideshow/source/engine/transitions/ellipsewipe.hxx @@ -0,0 +1,43 @@ +/* -*- 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_TRANSITIONS_ELLIPSEWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_ELLIPSEWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generate an iris wipe +class EllipseWipe : public ParametricPolyPolygon +{ +public: + explicit EllipseWipe( sal_Int32 nSubType ): mnSubType( nSubType ) {} + virtual ::basegfx::B2DPolyPolygon operator () ( double x ) override; +private: + sal_Int32 mnSubType; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_ELLIPSEWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/fanwipe.cxx b/slideshow/source/engine/transitions/fanwipe.cxx new file mode 100644 index 000000000..b86b0def2 --- /dev/null +++ b/slideshow/source/engine/transitions/fanwipe.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sal/log.hxx> +#include "transitiontools.hxx" +#include "clockwipe.hxx" +#include "fanwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon FanWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res; + ::basegfx::B2DPolygon poly( + ClockWipe::calcCenteredClock( + t / ((m_center && m_single) ? 2.0 : 4.0) ) ); + + res.append( poly ); + // flip on y-axis: + poly.transform(basegfx::utils::createScaleB2DHomMatrix(-1.0, 1.0)); + poly.flip(); + res.append( poly ); + + if (m_center) + { + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(0.5, 0.5, 0.5, 0.5)); + + if (! m_single) + res.append( flipOnXAxis(res) ); + } + else + { + SAL_WARN_IF( m_fanIn, "slideshow.opengl", "FanWipe: m_fanIn is true ?" ); + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(0.5, 1.0, 0.5, 1.0)); + } + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/fanwipe.hxx b/slideshow/source/engine/transitions/fanwipe.hxx new file mode 100644 index 000000000..99434082e --- /dev/null +++ b/slideshow/source/engine/transitions/fanwipe.hxx @@ -0,0 +1,44 @@ +/* -*- 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_TRANSITIONS_FANWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FANWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generates a centerTop (center=true) or double fan wipe: +class FanWipe : public ParametricPolyPolygon +{ +public: + FanWipe( bool center, bool single = true, bool fanIn = false ) + : m_center(center), m_single(single), m_fanIn(fanIn) {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + bool m_center, m_single, m_fanIn; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FANWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/figurewipe.cxx b/slideshow/source/engine/transitions/figurewipe.cxx new file mode 100644 index 000000000..f64145d55 --- /dev/null +++ b/slideshow/source/engine/transitions/figurewipe.cxx @@ -0,0 +1,117 @@ +/* -*- 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 <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "figurewipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon FigureWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res(m_figure); + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(t, t, 0.5, 0.5)); + return res; +} + +std::shared_ptr<FigureWipe> FigureWipe::createTriangleWipe() +{ + const double s60 = sin( basegfx::deg2rad(60.0) ); + const double s30 = sin( basegfx::deg2rad(30.0) ); + ::basegfx::B2DPolygon figure; + figure.append( ::basegfx::B2DPoint( 0.5 + s30, 0.5 ) ); + figure.append( ::basegfx::B2DPoint( 0.0, -0.5 - s60 ) ); + figure.append( ::basegfx::B2DPoint( -0.5 - s30, 0.5 ) ); + figure.setClosed(true); + return std::make_shared<FigureWipe>(figure); +} + +std::shared_ptr<FigureWipe> FigureWipe::createArrowHeadWipe() +{ + const double s60 = sin( basegfx::deg2rad(60.0) ); + const double s30 = sin( basegfx::deg2rad(30.0) ); + const double off = s30; + ::basegfx::B2DPolygon figure; + figure.append( ::basegfx::B2DPoint( 0.5 + s30 + off, 0.5 + off ) ); + figure.append( ::basegfx::B2DPoint( 0.0, -0.5 - s60 ) ); + figure.append( ::basegfx::B2DPoint( -0.5 - s30 - off, 0.5 + off ) ); + figure.append( ::basegfx::B2DPoint( 0.0, 0.5 ) ); + figure.setClosed(true); + return std::make_shared<FigureWipe>(figure); +} + +std::shared_ptr<FigureWipe> FigureWipe::createPentagonWipe() +{ + const double s = sin( basegfx::deg2rad(18.0) ); + const double c = cos( basegfx::deg2rad(18.0) ); + ::basegfx::B2DPolygon figure; + figure.append( ::basegfx::B2DPoint( 0.5, 0.5 ) ); + figure.append( ::basegfx::B2DPoint( 0.5 + s, 0.5 - c ) ); + figure.append( ::basegfx::B2DPoint( 0.0, 0.5 - c - sin(basegfx::deg2rad(36.0)) ) ); + figure.append( ::basegfx::B2DPoint( -0.5 - s, 0.5 - c ) ); + figure.append( ::basegfx::B2DPoint( -0.5, 0.5 ) ); + figure.setClosed(true); + return std::make_shared<FigureWipe>(figure); +} + +std::shared_ptr<FigureWipe> FigureWipe::createHexagonWipe() +{ + const double s = sin( basegfx::deg2rad(30.0) ); + const double c = cos( basegfx::deg2rad(30.0) ); + ::basegfx::B2DPolygon figure; + figure.append( ::basegfx::B2DPoint( 0.5, c ) ); + figure.append( ::basegfx::B2DPoint( 0.5 + s, 0.0 ) ); + figure.append( ::basegfx::B2DPoint( 0.5, -c ) ); + figure.append( ::basegfx::B2DPoint( -0.5, -c ) ); + figure.append( ::basegfx::B2DPoint( -0.5 - s, 0.0 ) ); + figure.append( ::basegfx::B2DPoint( -0.5, c ) ); + figure.setClosed(true); + return std::make_shared<FigureWipe>(figure); +} + +std::shared_ptr<FigureWipe> FigureWipe::createStarWipe( sal_Int32 nPoints ) +{ + const double v = M_PI / nPoints; + const ::basegfx::B2DPoint p_( 0.0, -M_SQRT2 ); + ::basegfx::B2DPolygon figure; + for ( sal_Int32 pos = 0; pos < nPoints; ++pos ) { + const double w = pos * 2.0 * M_PI / nPoints; + ::basegfx::B2DHomMatrix aTransform; + ::basegfx::B2DPoint p(p_); + aTransform.rotate( -w ); + p *= aTransform; + figure.append(p); + p = p_; + aTransform.identity(); + aTransform.scale( 0.5, 0.5 ); + aTransform.rotate( -w - v ); + p *= aTransform; + figure.append(p); + } + figure.setClosed(true); + return std::make_shared<FigureWipe>(figure); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/figurewipe.hxx b/slideshow/source/engine/transitions/figurewipe.hxx new file mode 100644 index 000000000..742d2dde2 --- /dev/null +++ b/slideshow/source/engine/transitions/figurewipe.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_TRANSITIONS_FIGUREWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FIGUREWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +class FigureWipe : public ParametricPolyPolygon +{ +public: + static std::shared_ptr<FigureWipe> createTriangleWipe(); + static std::shared_ptr<FigureWipe> createArrowHeadWipe(); + static std::shared_ptr<FigureWipe> createStarWipe( sal_Int32 nPoints ); + static std::shared_ptr<FigureWipe> createPentagonWipe(); + static std::shared_ptr<FigureWipe> createHexagonWipe(); + + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; + explicit FigureWipe( ::basegfx::B2DPolygon const & figure ) : m_figure(figure) {} +private: + const ::basegfx::B2DPolygon m_figure; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FIGUREWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/fourboxwipe.cxx b/slideshow/source/engine/transitions/fourboxwipe.cxx new file mode 100644 index 000000000..74833c2e0 --- /dev/null +++ b/slideshow/source/engine/transitions/fourboxwipe.cxx @@ -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 . + */ + + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "fourboxwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon FourBoxWipe::operator () ( double t ) +{ + ::basegfx::B2DHomMatrix aTransform; + const double d = ::basegfx::pruneScaleValue( t / 2.0 ); + if (m_cornersOut) + { + aTransform = basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5); + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(d, d, -0.25, -0.25) + * aTransform; + } + else + { + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(d, d, -0.5, -0.5); + } + + // top left: + ::basegfx::B2DPolygon square( m_unitRect ); + square.transform( aTransform ); + ::basegfx::B2DPolyPolygon res( square ); + // bottom left, flip on x-axis: + aTransform.scale( -1.0, 1.0 ); + ::basegfx::B2DPolygon square2( m_unitRect ); + square2.transform( aTransform ); + square2.flip(); // flip direction + res.append( square2 ); + // bottom right, flip on y-axis: + aTransform.scale( 1.0, -1.0 ); + ::basegfx::B2DPolygon square3( m_unitRect ); + square3.transform( aTransform ); + res.append( square3 ); + // top right, flip on x-axis: + aTransform.scale( -1.0, 1.0 ); + ::basegfx::B2DPolygon square4( m_unitRect ); + square4.transform( aTransform ); + square4.flip(); // flip direction + res.append( square4 ); + + aTransform = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5); + res.transform( aTransform ); + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/fourboxwipe.hxx b/slideshow/source/engine/transitions/fourboxwipe.hxx new file mode 100644 index 000000000..b2a501da7 --- /dev/null +++ b/slideshow/source/engine/transitions/fourboxwipe.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_TRANSITIONS_FOURBOXWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FOURBOXWIPE_HXX + +#include "parametricpolypolygon.hxx" +#include "transitiontools.hxx" +#include <basegfx/polygon/b2dpolygon.hxx> + + +namespace slideshow::internal { + +/// Generate a 4-box wipe +class FourBoxWipe : public ParametricPolyPolygon +{ +public: + explicit FourBoxWipe( bool cornersOut ) : m_cornersOut(cornersOut), + m_unitRect( createUnitRect() ) + {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const bool m_cornersOut; + const ::basegfx::B2DPolygon m_unitRect; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_FOURBOXWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/iriswipe.cxx b/slideshow/source/engine/transitions/iriswipe.cxx new file mode 100644 index 000000000..ebceddb19 --- /dev/null +++ b/slideshow/source/engine/transitions/iriswipe.cxx @@ -0,0 +1,38 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "iriswipe.hxx" + +namespace slideshow::internal +{ +::basegfx::B2DPolyPolygon IrisWipe::operator()(double t) +{ + const double d = ::basegfx::pruneScaleValue(t); + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5)); + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(d, d, 0.5, 0.5) * aTransform; + + ::basegfx::B2DPolyPolygon res(m_unitRect); + res.transform(aTransform); + return res; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/iriswipe.hxx b/slideshow/source/engine/transitions/iriswipe.hxx new file mode 100644 index 000000000..486adf988 --- /dev/null +++ b/slideshow/source/engine/transitions/iriswipe.hxx @@ -0,0 +1,46 @@ +/* -*- 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_TRANSITIONS_IRISWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_IRISWIPE_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include "parametricpolypolygon.hxx" +#include "transitiontools.hxx" + +namespace slideshow::internal +{ +/// Generate an iris wipe +class IrisWipe : public ParametricPolyPolygon +{ +public: + IrisWipe() + : m_unitRect(createUnitRect()) + { + } + virtual ::basegfx::B2DPolyPolygon operator()(double x) override; + +private: + const ::basegfx::B2DPolyPolygon m_unitRect; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_IRISWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/parametricpolypolygon.hxx b/slideshow/source/engine/transitions/parametricpolypolygon.hxx new file mode 100644 index 000000000..4e2ebb98e --- /dev/null +++ b/slideshow/source/engine/transitions/parametricpolypolygon.hxx @@ -0,0 +1,89 @@ +/* -*- 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_TRANSITIONS_PARAMETRICPOLYPOLYGON_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PARAMETRICPOLYPOLYGON_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <memory> + + +/* Definition of ParametricPolyPolygon interface */ + +namespace slideshow::internal + { + /** Interface defining a parametric poly-polygon. + + This interface defines a poly-polygon, whose actual shape + is parameterized by a floating point value. This is + e.g. used to generically access the various clip polygon + generators for transition effects. + + Since for every parametric poly-polygon, there is a set of + variations, which can easily be generated by simple + transformations or change in parameter range sweep + direction, objects implementing this interface only + generate <em>one</em> prototypical instance of the + parametric poly-polygon. Generally speaking, the main + effect direction should be horizontal, it should make + increasingly more area visible (transition 'in'), and when + there is a designated direction given, that should be + left-to-right. + */ + class ParametricPolyPolygon + { + public: + virtual ~ParametricPolyPolygon() {} + + /** Retrieve the poly-polygon for value t. + + @param t + Current parameter value to retrieve the corresponding + poly-polygon for. Permissible values for t must be in + the range [0,1]. + + @return a poly-polygon corresponding to the given + parameter value. The poly-polygon is interpreted as + living in the unit rectangle (i.e. [0,1]x[0,1]), but + is not necessarily constrained to completely lie in + this area (this very much depends on the actual effect + to be generated). Although, from a performance + perspective, it currently <em>is</em> advantageous to + try to keep the poly-polygon within these bounds (at + least if there are no hard reasons not to do so), + because then reversion or out transformations are + potentially faster to compute (see the + TransitionInfo::meReverseMethod member in + transitionfactory.cxx). Furthermore, if one of the + polygon modifications involve subtraction (also see + TransitionInfo::meReverseMethod), all generated + polygons should be oriented clock-wise + (i.e. traversing the polygon vertices with increasing + vertex index should generate a clock-wise movement). + */ + virtual ::basegfx::B2DPolyPolygon operator()( double t ) = 0; + }; + + typedef ::std::shared_ptr< ParametricPolyPolygon > ParametricPolyPolygonSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PARAMETRICPOLYPOLYGON_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/parametricpolypolygonfactory.cxx b/slideshow/source/engine/transitions/parametricpolypolygonfactory.cxx new file mode 100644 index 000000000..773f76aea --- /dev/null +++ b/slideshow/source/engine/transitions/parametricpolypolygonfactory.cxx @@ -0,0 +1,269 @@ +/* -*- 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/TransitionType.hpp> +#include <com/sun/star/animations/TransitionSubType.hpp> + +#include "parametricpolypolygonfactory.hxx" +#include "barwipepolypolygon.hxx" +#include "boxwipe.hxx" +#include "fourboxwipe.hxx" +#include "barndoorwipe.hxx" +#include "doublediamondwipe.hxx" +#include "veewipe.hxx" +#include "iriswipe.hxx" +#include "ellipsewipe.hxx" +#include "checkerboardwipe.hxx" +#include "randomwipe.hxx" +#include "waterfallwipe.hxx" +#include "clockwipe.hxx" +#include "fanwipe.hxx" +#include "pinwheelwipe.hxx" +#include "snakewipe.hxx" +#include "spiralwipe.hxx" +#include "sweepwipe.hxx" +#include "figurewipe.hxx" +#include "zigzagwipe.hxx" + + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + ParametricPolyPolygonSharedPtr + ParametricPolyPolygonFactory::createClipPolyPolygon( + sal_Int16 nType, sal_Int16 nSubType ) + { + using namespace ::com::sun::star::animations::TransitionType; + using namespace ::com::sun::star::animations::TransitionSubType; + + switch (nType) + { + case BARWIPE: + return std::make_shared<BarWipePolyPolygon>(); + case BLINDSWIPE: + return std::make_shared<BarWipePolyPolygon>( 6 ); + case BOXWIPE: + return std::make_shared<BoxWipe>( nSubType == LEFTCENTER || + nSubType == TOPCENTER || + nSubType == RIGHTCENTER|| + nSubType == BOTTOMCENTER ); + case FOURBOXWIPE: + return std::make_shared<FourBoxWipe>( nSubType == CORNERSOUT ); + case BARNDOORWIPE: + return std::make_shared<BarnDoorWipe>(); + case DIAGONALWIPE: + return std::make_shared<BarWipePolyPolygon>(); + case VEEWIPE: + return std::make_shared<VeeWipe>(); + case IRISWIPE: + return std::make_shared<IrisWipe>(); + case ELLIPSEWIPE: + return std::make_shared<EllipseWipe>(nSubType); + case CHECKERBOARDWIPE: + return std::make_shared<CheckerBoardWipe>(); + case RANDOMBARWIPE: + return std::make_shared<RandomWipe>( 128, true /* bars */ ); + case DISSOLVE: + return std::make_shared<RandomWipe>( 16 * 16, // for now until dxcanvas is faster +// 64 * 64 /* elements */, + false /* dissolve */ ); + case WATERFALLWIPE: + return std::make_shared<WaterfallWipe>( + 128, + // flipOnYAxis: + nSubType == VERTICALRIGHT || + nSubType == HORIZONTALLEFT ); + case CLOCKWIPE: + return std::make_shared<ClockWipe>(); + case FANWIPE: + return std::make_shared<FanWipe>( // center: + nSubType == CENTERTOP || + nSubType == CENTERRIGHT ); + case PINWHEELWIPE: { + sal_Int32 blades; + switch (nSubType) { + case ONEBLADE: + blades = 1; + break; + case THREEBLADE: + blades = 3; + break; + case FOURBLADE: + blades = 4; + break; + case EIGHTBLADE: + blades = 8; + break; + default: + blades = 2; + break; + } + return std::make_shared<PinWheelWipe>( blades ); + } + case SNAKEWIPE: + return std::make_shared<SnakeWipe>( + // elements: + 8 * 8, + // diagonal: + nSubType == TOPLEFTDIAGONAL || + nSubType == TOPRIGHTDIAGONAL || + nSubType == BOTTOMRIGHTDIAGONAL || + nSubType == BOTTOMLEFTDIAGONAL, + // flipOnYAxis: + nSubType == TOPLEFTVERTICAL || + nSubType == TOPRIGHTDIAGONAL || + nSubType == BOTTOMLEFTDIAGONAL + ); + case PARALLELSNAKESWIPE: + return std::make_shared<ParallelSnakesWipe>( + // elements: + 8 * 8, + // diagonal: + nSubType == DIAGONALBOTTOMLEFTOPPOSITE || + nSubType == DIAGONALTOPLEFTOPPOSITE, + // flipOnYAxis: + nSubType == VERTICALBOTTOMLEFTOPPOSITE || + nSubType == HORIZONTALTOPLEFTOPPOSITE || + nSubType == DIAGONALTOPLEFTOPPOSITE, + // opposite: + nSubType == VERTICALTOPLEFTOPPOSITE || + nSubType == VERTICALBOTTOMLEFTOPPOSITE || + nSubType == HORIZONTALTOPLEFTOPPOSITE || + nSubType == HORIZONTALTOPRIGHTOPPOSITE || + nSubType == DIAGONALBOTTOMLEFTOPPOSITE || + nSubType == DIAGONALTOPLEFTOPPOSITE + ); + case SPIRALWIPE: + return std::make_shared<SpiralWipe>( + // elements: + 8 * 8, + // flipOnYAxis: + nSubType == TOPLEFTCOUNTERCLOCKWISE || + nSubType == TOPRIGHTCOUNTERCLOCKWISE || + nSubType == BOTTOMRIGHTCOUNTERCLOCKWISE || + nSubType == BOTTOMLEFTCOUNTERCLOCKWISE ); + case BOXSNAKESWIPE: + return std::make_shared<BoxSnakesWipe>( + // elements: + 8 * 8, + // fourBox: + nSubType == FOURBOXVERTICAL || + nSubType == FOURBOXHORIZONTAL ); + case SINGLESWEEPWIPE: + return std::make_shared<SweepWipe>( + // center: + nSubType == CLOCKWISETOP || + nSubType == CLOCKWISERIGHT || + nSubType == CLOCKWISEBOTTOM || + nSubType == CLOCKWISELEFT, + // single: + true, + // oppositeVertical: + false, + // flipOnYAxis: + nSubType == COUNTERCLOCKWISEBOTTOMLEFT || + nSubType == COUNTERCLOCKWISETOPRIGHT + ); + case DOUBLESWEEPWIPE: + return std::make_shared<SweepWipe>( + // center: + nSubType == PARALLELVERTICAL || + nSubType == PARALLELDIAGONAL || + nSubType == OPPOSITEVERTICAL || + nSubType == OPPOSITEHORIZONTAL, + // single: + false, + // oppositeVertical: + nSubType == OPPOSITEVERTICAL || + nSubType == OPPOSITEHORIZONTAL, + // flipOnYAxis: + false ); + case DOUBLEFANWIPE: + return std::make_shared<FanWipe>( + //center: + true, + // single: + false, + // fanIn: + nSubType == FANINVERTICAL || + nSubType == FANINHORIZONTAL ); + case TRIANGLEWIPE: + return FigureWipe::createTriangleWipe(); + case ARROWHEADWIPE: + return FigureWipe::createArrowHeadWipe(); + case PENTAGONWIPE: + return FigureWipe::createPentagonWipe(); + case HEXAGONWIPE: + return FigureWipe::createHexagonWipe(); + case STARWIPE: { + sal_Int32 points; + switch (nSubType) { + case FIVEPOINT: + points = 5; + break; + case SIXPOINT: + points = 6; + break; + default: + points = 4; + break; + } + return FigureWipe::createStarWipe(points); + } + case MISCDIAGONALWIPE: { + switch (nSubType) { + case DOUBLEBARNDOOR: + return std::make_shared<BarnDoorWipe>( true /* doubled */ ); + case DOUBLEDIAMOND: + return std::make_shared<DoubleDiamondWipe>(); + } + break; + } + case ZIGZAGWIPE: + return std::make_shared<ZigZagWipe>(5); + case BARNZIGZAGWIPE: + return std::make_shared<BarnZigZagWipe>(5); + + case BOWTIEWIPE: + case BARNVEEWIPE: + case EYEWIPE: + case ROUNDRECTWIPE: + case MISCSHAPEWIPE: + case SALOONDOORWIPE: + case WINDSHIELDWIPE: + // for now, map to barwipe transition + return std::make_shared<BarWipePolyPolygon>(); + + default: + case PUSHWIPE: + case SLIDEWIPE: + case FADE: + ENSURE_OR_THROW( false, + "createShapeClipPolyPolygonAnimation(): Transition type mismatch" ); + } + + return ParametricPolyPolygonSharedPtr(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/parametricpolypolygonfactory.hxx b/slideshow/source/engine/transitions/parametricpolypolygonfactory.hxx new file mode 100644 index 000000000..7a0e7aacd --- /dev/null +++ b/slideshow/source/engine/transitions/parametricpolypolygonfactory.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PARAMETRICPOLYPOLYGONFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PARAMETRICPOLYPOLYGONFACTORY_HXX + +#include "parametricpolypolygon.hxx" + +namespace slideshow::internal + { + /* Definition of Transitionfactory class */ + + namespace ParametricPolyPolygonFactory + { + ParametricPolyPolygonSharedPtr createClipPolyPolygon( sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType ); + } + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PARAMETRICPOLYPOLYGONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/pinwheelwipe.cxx b/slideshow/source/engine/transitions/pinwheelwipe.cxx new file mode 100644 index 000000000..74522a8e6 --- /dev/null +++ b/slideshow/source/engine/transitions/pinwheelwipe.cxx @@ -0,0 +1,46 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrixtools.hxx> +#include "clockwipe.hxx" +#include "pinwheelwipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon PinWheelWipe::operator () ( double t ) +{ + ::basegfx::B2DPolygon poly( ClockWipe::calcCenteredClock( + t / m_blades, + 2.0 /* max edge when rotating */ ) ); + ::basegfx::B2DPolyPolygon res; + for ( sal_Int32 i = m_blades; i--; ) + { + ::basegfx::B2DPolygon p(poly); + p.transform(basegfx::utils::createRotateB2DHomMatrix((i * 2.0 * M_PI) / m_blades)); + res.append( p ); + } + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(0.5, 0.5, 0.5, 0.5)); + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/pinwheelwipe.hxx b/slideshow/source/engine/transitions/pinwheelwipe.hxx new file mode 100644 index 000000000..d1daae126 --- /dev/null +++ b/slideshow/source/engine/transitions/pinwheelwipe.hxx @@ -0,0 +1,43 @@ +/* -*- 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_TRANSITIONS_PINWHEELWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PINWHEELWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generates a n-blade pinWheel wipe: +class PinWheelWipe : public ParametricPolyPolygon +{ +public: + explicit PinWheelWipe( sal_Int32 blades ) : m_blades(blades) {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + sal_Int32 m_blades; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_PINWHEELWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/randomwipe.cxx b/slideshow/source/engine/transitions/randomwipe.cxx new file mode 100644 index 000000000..58047a676 --- /dev/null +++ b/slideshow/source/engine/transitions/randomwipe.cxx @@ -0,0 +1,82 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "randomwipe.hxx" +#include "transitiontools.hxx" +#include <tools.hxx> + + +namespace slideshow::internal { + +RandomWipe::RandomWipe( sal_Int32 nElements, bool randomBars ) + : m_positions( new ::basegfx::B2DPoint[ nElements ] ), + m_nElements( nElements ), + m_rect( createUnitRect() ) +{ + ::basegfx::B2DHomMatrix aTransform; + if (randomBars) + { + double edge = 1.0 / nElements; + for ( sal_Int32 pos = nElements; pos--; ) + m_positions[ pos ].setY( ::basegfx::pruneScaleValue( pos * edge ) ); + aTransform.scale( 1.0, ::basegfx::pruneScaleValue(edge) ); + } + else // dissolve effect + { + sal_Int32 sqrtElements = static_cast<sal_Int32>( + sqrt( static_cast<double>(nElements) ) ); + double edge = 1.0 / sqrtElements; + for ( sal_Int32 pos = nElements; pos--; ) { + m_positions[ pos ] = ::basegfx::B2DPoint( + ::basegfx::pruneScaleValue( (pos % sqrtElements) * edge ), + ::basegfx::pruneScaleValue( (pos / sqrtElements) * edge ) ); + } + const double pedge = ::basegfx::pruneScaleValue(edge); + aTransform.scale( pedge, pedge ); + } + m_rect.transform( aTransform ); + + // mix up: + for ( sal_Int32 pos1 = nElements ; pos1-- ; ) + { + const sal_Int32 pos2 = getRandomOrdinal(pos1+1); + ::std::swap(m_positions[ pos1], m_positions[ pos2 ]); + } +} + +::basegfx::B2DPolyPolygon RandomWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res; + for ( sal_Int32 pos = static_cast<sal_Int32>(t * m_nElements); pos--; ) + { + ::basegfx::B2DPoint const & point = m_positions[ pos ]; + ::basegfx::B2DPolygon poly( m_rect ); + poly.transform(basegfx::utils::createTranslateB2DHomMatrix(point.getX(), point.getY())); + res.append( poly ); + } + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/randomwipe.hxx b/slideshow/source/engine/transitions/randomwipe.hxx new file mode 100644 index 000000000..52939910c --- /dev/null +++ b/slideshow/source/engine/transitions/randomwipe.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_TRANSITIONS_RANDOMWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_RANDOMWIPE_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <memory> + +#include "parametricpolypolygon.hxx" + +namespace slideshow::internal +{ +class RandomWipe : public ParametricPolyPolygon +{ +public: + RandomWipe(sal_Int32 nElements, bool randomBars /* true: generates a horizontal random bar wipe, + false: generates a dissolve wipe */); + virtual ::basegfx::B2DPolyPolygon operator()(double t) override; + +private: + ::std::unique_ptr<::basegfx::B2DPoint[]> m_positions; + sal_Int32 m_nElements; + ::basegfx::B2DPolygon m_rect; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_RANDOMWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/shapetransitionfactory.cxx b/slideshow/source/engine/transitions/shapetransitionfactory.cxx new file mode 100644 index 000000000..3586cff71 --- /dev/null +++ b/slideshow/source/engine/transitions/shapetransitionfactory.cxx @@ -0,0 +1,370 @@ +/* -*- 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 <sal/log.hxx> + +#include <com/sun/star/animations/TransitionType.hpp> +#include <com/sun/star/animations/TransitionSubType.hpp> + +#include <transitionfactory.hxx> +#include "transitionfactorytab.hxx" +#include "parametricpolypolygonfactory.hxx" +#include <animationfactory.hxx> +#include "clippingfunctor.hxx" + +using namespace ::com::sun::star; + +namespace slideshow::internal { + +/*************************************************** + *** *** + *** Shape Transition Effects *** + *** *** + ***************************************************/ + +namespace { + +class ClippingAnimation : public NumberAnimation +{ +public: + ClippingAnimation( + const ParametricPolyPolygonSharedPtr& rPolygon, + const ShapeManagerSharedPtr& rShapeManager, + const TransitionInfo& rTransitionInfo, + bool bDirectionForward, + bool bModeIn ); + + virtual ~ClippingAnimation() override; + + // Animation interface + + virtual void prefetch() override; + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) override; + virtual void end() override; + + // NumberAnimation interface + + virtual bool operator()( double nValue ) override; + virtual double getUnderlyingValue() const override; + +private: + void end_(); + + AnimatableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttrLayer; + ShapeManagerSharedPtr mpShapeManager; + ClippingFunctor maClippingFunctor; + bool mbSpriteActive; +}; + +ClippingAnimation::ClippingAnimation( + const ParametricPolyPolygonSharedPtr& rPolygon, + const ShapeManagerSharedPtr& rShapeManager, + const TransitionInfo& rTransitionInfo, + bool bDirectionForward, + bool bModeIn ) : + mpShape(), + mpAttrLayer(), + mpShapeManager( rShapeManager ), + maClippingFunctor( rPolygon, + rTransitionInfo, + bDirectionForward, + bModeIn ), + mbSpriteActive(false) +{ + ENSURE_OR_THROW( + rShapeManager, + "ClippingAnimation::ClippingAnimation(): Invalid ShapeManager" ); +} + +ClippingAnimation::~ClippingAnimation() +{ + try + { + end_(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } +} + +void ClippingAnimation::prefetch() +{ +} + +void ClippingAnimation::start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) +{ + OSL_ENSURE( !mpShape, + "ClippingAnimation::start(): Shape already set" ); + OSL_ENSURE( !mpAttrLayer, + "ClippingAnimation::start(): Attribute layer already set" ); + ENSURE_OR_THROW( rShape, + "ClippingAnimation::start(): Invalid shape" ); + ENSURE_OR_THROW( rAttrLayer, + "ClippingAnimation::start(): Invalid attribute layer" ); + + mpShape = rShape; + mpAttrLayer = rAttrLayer; + + if( !mbSpriteActive ) + { + mpShapeManager->enterAnimationMode( mpShape ); + mbSpriteActive = true; + } +} + +void ClippingAnimation::end() +{ + end_(); +} + +void ClippingAnimation::end_() +{ + if( mbSpriteActive ) + { + mbSpriteActive = false; + mpShapeManager->leaveAnimationMode( mpShape ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + } +} + +bool ClippingAnimation::operator()( double nValue ) +{ + ENSURE_OR_RETURN_FALSE( + mpAttrLayer && mpShape, + "ClippingAnimation::operator(): Invalid ShapeAttributeLayer" ); + + // set new clip + mpAttrLayer->setClip( maClippingFunctor( nValue, + mpShape->getDomBounds().getRange() ) ); + + if( mpShape->isContentChanged() ) + mpShapeManager->notifyShapeUpdate( mpShape ); + + return true; +} + +double ClippingAnimation::getUnderlyingValue() const +{ + ENSURE_OR_THROW( + mpAttrLayer, + "ClippingAnimation::getUnderlyingValue(): Invalid ShapeAttributeLayer" ); + + return 0.0; // though this should be used in concert with + // ActivitiesFactory::createSimpleActivity, better + // explicitly name our start value. + // Permissible range for operator() above is [0,1] +} + +AnimationActivitySharedPtr createShapeTransitionByType( + const ActivitiesFactory::CommonParameters& rParms, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + css::uno::Reference< css::animations::XTransitionFilter > const& xTransition, + sal_Int16 nType, + sal_Int16 nSubType ) +{ + ENSURE_OR_THROW( + xTransition.is(), + "createShapeTransitionByType(): Invalid XTransition" ); + + const TransitionInfo* pTransitionInfo( + getTransitionInfo( nType, nSubType ) ); + + AnimationActivitySharedPtr pGeneratedActivity; + if( pTransitionInfo != nullptr ) + { + switch( pTransitionInfo->meTransitionClass ) + { + default: + case TransitionInfo::TRANSITION_INVALID: + OSL_FAIL( "createShapeTransitionByType(): Invalid transition type. " + "Don't ask me for a 0 TransitionType, have no XTransitionFilter node instead!" ); + return AnimationActivitySharedPtr(); + + + case TransitionInfo::TRANSITION_CLIP_POLYPOLYGON: + { + // generate parametric poly-polygon + ParametricPolyPolygonSharedPtr pPoly( + ParametricPolyPolygonFactory::createClipPolyPolygon( + nType, nSubType ) ); + + // create a clip activity from that + pGeneratedActivity = ActivitiesFactory::createSimpleActivity( + rParms, + std::make_shared<ClippingAnimation>( + pPoly, + rShapeManager, + *pTransitionInfo, + xTransition->getDirection(), + xTransition->getMode() ), + true ); + } + break; + + case TransitionInfo::TRANSITION_SPECIAL: + { + switch( nType ) + { + case animations::TransitionType::RANDOM: + { + // select randomly one of the effects from the + // TransitionFactoryTable + + const TransitionInfo* pRandomTransitionInfo( getRandomTransitionInfo() ); + + ENSURE_OR_THROW( pRandomTransitionInfo != nullptr, + "createShapeTransitionByType(): Got invalid random transition info" ); + + ENSURE_OR_THROW( pRandomTransitionInfo->mnTransitionType != animations::TransitionType::RANDOM, + "createShapeTransitionByType(): Got random again for random input!" ); + + // and recurse + pGeneratedActivity = createShapeTransitionByType( rParms, + rShape, + rShapeManager, + rSlideSize, + xTransition, + pRandomTransitionInfo->mnTransitionType, + pRandomTransitionInfo->mnTransitionSubType ); + } + break; + + // TODO(F3): Implement slidewipe for shape + case animations::TransitionType::SLIDEWIPE: + { + sal_Int16 nBarWipeSubType(0); + bool bDirectionForward(true); + + // map slidewipe to BARWIPE, for now + switch( nSubType ) + { + case animations::TransitionSubType::FROMLEFT: + nBarWipeSubType = animations::TransitionSubType::LEFTTORIGHT; + bDirectionForward = true; + break; + + case animations::TransitionSubType::FROMRIGHT: + nBarWipeSubType = animations::TransitionSubType::LEFTTORIGHT; + bDirectionForward = false; + break; + + case animations::TransitionSubType::FROMTOP: + nBarWipeSubType = animations::TransitionSubType::TOPTOBOTTOM; + bDirectionForward = true; + break; + + case animations::TransitionSubType::FROMBOTTOM: + nBarWipeSubType = animations::TransitionSubType::TOPTOBOTTOM; + bDirectionForward = false; + break; + + default: + ENSURE_OR_THROW( false, + "createShapeTransitionByType(): Unexpected subtype for SLIDEWIPE" ); + break; + } + + // generate parametric poly-polygon + ParametricPolyPolygonSharedPtr pPoly( + ParametricPolyPolygonFactory::createClipPolyPolygon( + animations::TransitionType::BARWIPE, + nBarWipeSubType ) ); + + // create a clip activity from that + pGeneratedActivity = ActivitiesFactory::createSimpleActivity( + rParms, + std::make_shared<ClippingAnimation>( + pPoly, + rShapeManager, + *getTransitionInfo( animations::TransitionType::BARWIPE, + nBarWipeSubType ), + bDirectionForward, + xTransition->getMode() ), + true ); + } + break; + + default: + { + // TODO(F1): Check whether there's anything left, anyway, + // for _shape_ transitions. AFAIK, there are no special + // effects for shapes... + + // for now, map all to fade effect + pGeneratedActivity = ActivitiesFactory::createSimpleActivity( + rParms, + AnimationFactory::createNumberPropertyAnimation( + "Opacity", + rShape, + rShapeManager, + rSlideSize, + nullptr ), + xTransition->getMode() ); + } + break; + } + } + break; + } + } + + if( !pGeneratedActivity ) + { + // No animation generated, maybe no table entry for given + // transition? + SAL_WARN("slideshow", + "createShapeTransitionByType(): Unknown type/subtype combination encountered: " + << xTransition->getTransition() << " " << xTransition->getSubtype() ); + } + + return pGeneratedActivity; +} + +} // anon namespace + +AnimationActivitySharedPtr TransitionFactory::createShapeTransition( + const ActivitiesFactory::CommonParameters& rParms, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + uno::Reference< animations::XTransitionFilter > const& xTransition ) +{ + return createShapeTransitionByType( rParms, + rShape, + rShapeManager, + rSlideSize, + xTransition, + xTransition->getTransition(), + xTransition->getSubtype() ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/slidechangebase.cxx b/slideshow/source/engine/transitions/slidechangebase.cxx new file mode 100644 index 000000000..460f53cf4 --- /dev/null +++ b/slideshow/source/engine/transitions/slidechangebase.cxx @@ -0,0 +1,510 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrixtools.hxx> +#include <cppcanvas/basegfxfactory.hxx> +#include <cppcanvas/customsprite.hxx> + +#include "slidechangebase.hxx" +#include <tools.hxx> + +#include <algorithm> + +using namespace com::sun::star; + +namespace slideshow::internal { + +SlideChangeBase::SlideChangeBase( std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + bool bCreateLeavingSprites, + bool bCreateEnteringSprites ) : + mpSoundPlayer( pSoundPlayer ), + mrEventMultiplexer(rEventMultiplexer), + mrScreenUpdater(rScreenUpdater), + maLeavingSlide( leavingSlide ), + mpEnteringSlide( pEnteringSlide ), + maViewData(), + mrViewContainer(rViewContainer), + mbCreateLeavingSprites(bCreateLeavingSprites), + mbCreateEnteringSprites(bCreateEnteringSprites), + mbSpritesVisible(false), + mbFinished(false), + mbPrefetched(false) +{ + ENSURE_OR_THROW( + pEnteringSlide, + "SlideChangeBase::SlideChangeBase(): Invalid entering slide!" ); +} + +SlideBitmapSharedPtr SlideChangeBase::getLeavingBitmap( const ViewEntry& rViewEntry ) const +{ + if( !rViewEntry.mpLeavingBitmap ) + rViewEntry.mpLeavingBitmap = createBitmap(rViewEntry.mpView, + maLeavingSlide); + + return rViewEntry.mpLeavingBitmap; +} + +SlideBitmapSharedPtr SlideChangeBase::getEnteringBitmap( const ViewEntry& rViewEntry ) const +{ + if( !rViewEntry.mpEnteringBitmap ) + rViewEntry.mpEnteringBitmap = createBitmap( rViewEntry.mpView, + std::optional<SlideSharedPtr>(mpEnteringSlide) ); + + return rViewEntry.mpEnteringBitmap; +} + +SlideBitmapSharedPtr SlideChangeBase::createBitmap( const UnoViewSharedPtr& rView, + const std::optional<SlideSharedPtr>& rSlide ) const +{ + SlideBitmapSharedPtr pRet; + if( !rSlide ) + return pRet; + + SlideSharedPtr const & pSlide = *rSlide; + if( !pSlide ) + { + // TODO(P3): No need to generate a bitmap here. This only made + // the code more uniform. Faster would be to simply clear the + // sprite to black. + + // create empty, black-filled bitmap + const basegfx::B2ISize slideSizePixel( + getSlideSizePixel( basegfx::B2DSize( mpEnteringSlide->getSlideSize() ), + rView )); + + cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() ); + + // create a bitmap of appropriate size + cppcanvas::BitmapSharedPtr pBitmap( + cppcanvas::BaseGfxFactory::createBitmap( + pCanvas, + slideSizePixel ) ); + + ENSURE_OR_THROW( + pBitmap, + "SlideChangeBase::createBitmap(): Cannot create page bitmap" ); + + cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( + pBitmap->getBitmapCanvas() ); + + ENSURE_OR_THROW( pBitmapCanvas, + "SlideChangeBase::createBitmap(): " + "Cannot create page bitmap canvas" ); + + // set transformation to identity (->device pixel) + pBitmapCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // clear bitmap to black + fillRect( pBitmapCanvas, + ::basegfx::B2DRectangle( 0.0, 0.0, + slideSizePixel.getX(), + slideSizePixel.getY() ), + 0x000000FFU ); + + pRet = std::make_shared<SlideBitmap>( pBitmap ); + } + else + { + pRet = pSlide->getCurrentSlideBitmap( rView ); + } + + return pRet; +} + +::basegfx::B2ISize SlideChangeBase::getEnteringSlideSizePixel( const UnoViewSharedPtr& pView ) const +{ + return getSlideSizePixel( basegfx::B2DSize( mpEnteringSlide->getSlideSize() ), + pView ); +} + +void SlideChangeBase::renderBitmap( + SlideBitmapSharedPtr const & pSlideBitmap, + cppcanvas::CanvasSharedPtr const & pCanvas ) +{ + if( !(pSlideBitmap && pCanvas) ) + return; + + // need to render without any transformation (we + // assume device units): + const basegfx::B2DHomMatrix viewTransform( + pCanvas->getTransformation() ); + const basegfx::B2DPoint pageOrigin( + viewTransform * basegfx::B2DPoint() ); + const cppcanvas::CanvasSharedPtr pDevicePixelCanvas( + pCanvas->clone() ); + + // render at output position, don't modify bitmap object (no move!): + const basegfx::B2DHomMatrix transform(basegfx::utils::createTranslateB2DHomMatrix( + pageOrigin.getX(), pageOrigin.getY())); + + pDevicePixelCanvas->setTransformation( transform ); + pSlideBitmap->draw( pDevicePixelCanvas ); +} + +void SlideChangeBase::prefetch() +{ + // we're a one-shot activity, and already finished + if( mbFinished || mbPrefetched ) + return; + + // register ourselves for view change events + mrEventMultiplexer.addViewHandler( std::dynamic_pointer_cast<ViewEventHandler>(shared_from_this()) ); + + // init views and create slide bitmaps + for( const auto& pView : mrViewContainer ) + viewAdded( pView ); + + mbPrefetched = true; +} + +void SlideChangeBase::start( const AnimatableShapeSharedPtr& /*rShape*/, + const ShapeAttributeLayerSharedPtr& /*rLayer*/ ) +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + prefetch(); // no-op, if already done + + // get the subclasses a chance to do any specific initialization before run + for ( ViewsVecT::const_iterator aCurr( beginViews() ), aEnd( endViews() ); aCurr != aEnd; ++aCurr ) + prepareForRun( *aCurr, aCurr->mpView->getCanvas() ); + + // start accompanying sound effect, if any + if( mpSoundPlayer ) + { + mpSoundPlayer->startPlayback(); + // xxx todo: for now, presentation.cxx takes care about the slide + // #i50492# transition sound object, so just release it here + mpSoundPlayer.reset(); + } +} + +void SlideChangeBase::end() +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + try + { + // draw fully entered bitmap: + ViewsVecT::const_iterator aCurr( beginViews() ); + const ViewsVecT::const_iterator aEnd( endViews() ); + while( aCurr != aEnd ) + { + // fully clear view content to background color + aCurr->mpView->clearAll(); + + const SlideBitmapSharedPtr pSlideBitmap( getEnteringBitmap( *aCurr )); + pSlideBitmap->clip( basegfx::B2DPolyPolygon() /* no clipping */ ); + aCurr->mpView->clearAll(); + renderBitmap( pSlideBitmap, + aCurr->mpView->getCanvas() ); + + ++aCurr; + } + } + catch( uno::Exception& ) + { + // make sure releasing below happens + } + + // swap changes to screen + mrScreenUpdater.notifyUpdate(); + + // make object dysfunctional + mbFinished = true; + ViewsVecT().swap(maViewData); + maLeavingSlide.reset(); + mpEnteringSlide.reset(); + + // sprites have been binned above + mbSpritesVisible = false; + + // remove also from event multiplexer, we're dead anyway + mrEventMultiplexer.removeViewHandler( std::dynamic_pointer_cast<ViewEventHandler>(shared_from_this()) ); +} + +bool SlideChangeBase::operator()( double nValue ) +{ + if( mbFinished ) + return false; + + const std::size_t nEntries( maViewData.size() ); + bool bSpritesVisible( mbSpritesVisible ); + + for( ::std::size_t i=0; i<nEntries; ++i ) + { + // calc sprite offsets. The enter/leaving bitmaps are only + // as large as the actual slides. For scaled-down + // presentations, we have to move the left, top edge of + // those bitmaps to the actual position, governed by the + // given view transform. The aSpritePosPixel local + // variable is already in device coordinate space + // (i.e. pixel). + + ViewEntry& rViewEntry( maViewData[i] ); + const ::cppcanvas::CanvasSharedPtr& rCanvas( rViewEntry.mpView->getCanvas() ); + ::cppcanvas::CustomSpriteSharedPtr& rInSprite( rViewEntry.mpInSprite ); + ::cppcanvas::CustomSpriteSharedPtr& rOutSprite( rViewEntry.mpOutSprite ); + + // TODO(F2): Properly respect clip here. + + // Might have to be transformed, too. + const ::basegfx::B2DHomMatrix aViewTransform( + rViewEntry.mpView->getTransformation() ); + const ::basegfx::B2DPoint aSpritePosPixel( + aViewTransform * ::basegfx::B2DPoint() ); + + // move sprite to final output position, in + // device coordinates + if( rOutSprite ) + rOutSprite->movePixel( aSpritePosPixel ); + if( rInSprite ) + rInSprite->movePixel( aSpritePosPixel ); + + if( !mbSpritesVisible ) + { + if( rOutSprite ) + { + // only render once: clipping is done + // exclusively with the sprite + const ::cppcanvas::CanvasSharedPtr pOutContentCanvas( + rOutSprite->getContentCanvas() ); + if( pOutContentCanvas) + { + // TODO(Q2): Use basegfx bitmaps here + + // TODO(F1): SlideBitmap is not fully portable + // between different canvases! + + // render the content + OSL_ASSERT( getLeavingBitmap( rViewEntry ) ); + if( getLeavingBitmap( rViewEntry ) ) + getLeavingBitmap( rViewEntry )->draw( pOutContentCanvas ); + } + } + + if( rInSprite ) + { + // only render once: clipping is done + // exclusively with the sprite + const ::cppcanvas::CanvasSharedPtr pInContentCanvas( + rInSprite->getContentCanvas() ); + if( pInContentCanvas ) + { + // TODO(Q2): Use basegfx bitmaps here + + // TODO(F1): SlideBitmap is not fully portable + // between different canvases! + + // render the content + getEnteringBitmap( rViewEntry )->draw( pInContentCanvas ); + } + } + } + + if( rOutSprite ) + performOut( rOutSprite, rViewEntry, rCanvas, nValue ); + if( rInSprite ) + performIn( rInSprite, rViewEntry, rCanvas, nValue ); + + // finishing deeds for first run. + if( !mbSpritesVisible) + { + // enable sprites: + if( rOutSprite ) + rOutSprite->show(); + if( rInSprite ) + rInSprite->show(); + bSpritesVisible = true; + } + } // for_each( sprite ) + + mbSpritesVisible = bSpritesVisible; + mrScreenUpdater.notifyUpdate(); + + return true; +} + +void SlideChangeBase::prepareForRun( + const ViewEntry& /* rViewEntry */, + const cppcanvas::CanvasSharedPtr& /* rDestinationCanvas */ ) +{ +} + +void SlideChangeBase::performIn( + const cppcanvas::CustomSpriteSharedPtr& /*rSprite*/, + const ViewEntry& /*rViewEntry*/, + const cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double /*t*/ ) +{ +} + +void SlideChangeBase::performOut( + const cppcanvas::CustomSpriteSharedPtr& /*rSprite*/, + const ViewEntry& /*rViewEntry*/, + const cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double /*t*/ ) +{ +} + +double SlideChangeBase::getUnderlyingValue() const +{ + return 0.0; // though this should be used in concert with + // ActivitiesFactory::createSimpleActivity, better + // explicitly name our start value. + // Permissible range for operator() above is [0,1] +} + +void SlideChangeBase::viewAdded( const UnoViewSharedPtr& rView ) +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + maViewData.emplace_back(rView ); + + ViewEntry& rEntry( maViewData.back() ); + getEnteringBitmap( rEntry ); + getLeavingBitmap( rEntry ); + addSprites( rEntry ); +} + +void SlideChangeBase::viewRemoved( const UnoViewSharedPtr& rView ) +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + // erase corresponding entry from maViewData + maViewData.erase( + std::remove_if( + maViewData.begin(), + maViewData.end(), + [rView]( const ViewEntry& rViewEntry ) + { return rView == rViewEntry.getView(); } ), + maViewData.end() ); +} + +void SlideChangeBase::viewChanged( const UnoViewSharedPtr& rView ) +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + // find entry corresponding to modified view + ViewsVecT::iterator aModifiedEntry( + std::find_if( + maViewData.begin(), + maViewData.end(), + [rView]( const ViewEntry& rViewEntry ) + { return rView == rViewEntry.getView(); } ) ); + + OSL_ASSERT( aModifiedEntry != maViewData.end() ); + if( aModifiedEntry == maViewData.end() ) + return; + + // clear stale info (both bitmaps and sprites prolly need a + // resize) + clearViewEntry( *aModifiedEntry ); + addSprites( *aModifiedEntry ); +} + +void SlideChangeBase::viewsChanged() +{ + // we're a one-shot activity, and already finished + if( mbFinished ) + return; + + for( auto& rView : maViewData ) + { + // clear stale info (both bitmaps and sprites prolly need a + // resize) + clearViewEntry( rView ); + addSprites( rView ); + } +} + +cppcanvas::CustomSpriteSharedPtr SlideChangeBase::createSprite( + UnoViewSharedPtr const & pView, + basegfx::B2DSize const & rSpriteSize, + double nPrio ) const +{ + // TODO(P2): change to bitmapsprite once that's working + const cppcanvas::CustomSpriteSharedPtr pSprite( + pView->createSprite( rSpriteSize, + nPrio )); + + // alpha default is 0.0, which seems to be + // a bad idea when viewing content... + pSprite->setAlpha( 1.0 ); + if (mbSpritesVisible) + pSprite->show(); + + return pSprite; +} + +void SlideChangeBase::addSprites( ViewEntry& rEntry ) +{ + if( mbCreateLeavingSprites && maLeavingSlide ) + { + // create leaving sprite: + const basegfx::B2ISize leavingSlideSizePixel( + getLeavingBitmap( rEntry )->getSize() ); + + rEntry.mpOutSprite = createSprite( rEntry.mpView, + basegfx::B2DSize( leavingSlideSizePixel ), + 100 ); + } + + if( mbCreateEnteringSprites ) + { + // create entering sprite: + const basegfx::B2ISize enteringSlideSizePixel( + getSlideSizePixel( basegfx::B2DSize( mpEnteringSlide->getSlideSize() ), + rEntry.mpView )); + + rEntry.mpInSprite = createSprite( rEntry.mpView, + basegfx::B2DSize( enteringSlideSizePixel ), + 101 ); + } +} + +void SlideChangeBase::clearViewEntry( ViewEntry& rEntry ) +{ + // clear stale info (both bitmaps and sprites prolly need a + // resize) + rEntry.mpEnteringBitmap.reset(); + rEntry.mpLeavingBitmap.reset(); + rEntry.mpInSprite.reset(); + rEntry.mpOutSprite.reset(); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/slidechangebase.hxx b/slideshow/source/engine/transitions/slidechangebase.hxx new file mode 100644 index 000000000..e80145df6 --- /dev/null +++ b/slideshow/source/engine/transitions/slidechangebase.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SLIDECHANGEBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SLIDECHANGEBASE_HXX + +#include <unoview.hxx> +#include <vieweventhandler.hxx> +#include <numberanimation.hxx> +#include <slide.hxx> +#include <screenupdater.hxx> +#include <soundplayer.hxx> + +#include <memory> +#include <optional> + +namespace cppcanvas +{ + class Canvas; + class CustomSprite; +} + +namespace slideshow::internal { + +/** Base class for all slide change effects. + + This class provides the basic sprite and view handling + functionality. Derived classes should normally only need to + implement the perform() method. +*/ +class SlideChangeBase : public ViewEventHandler, + public NumberAnimation +{ +public: + SlideChangeBase(const SlideChangeBase&) = delete; + SlideChangeBase& operator=(const SlideChangeBase&) = delete; + + // NumberAnimation + virtual bool operator()( double x ) override; + virtual double getUnderlyingValue() const override; + + // Animation + virtual void prefetch() override; + virtual void start( const AnimatableShapeSharedPtr&, + const ShapeAttributeLayerSharedPtr& ) override; + virtual void end() override; + + // ViewEventHandler + virtual void viewAdded( const UnoViewSharedPtr& rView ) override; + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override; + virtual void viewChanged( const UnoViewSharedPtr& rView ) override; + virtual void viewsChanged() override; + +protected: + /** Create a new SlideChanger, for the given leaving and + entering slides. + */ + SlideChangeBase( + ::std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + bool bCreateLeavingSprites = true, + bool bCreateEnteringSprites = true ); + + /// Info on a per-view basis + struct ViewEntry + { + explicit ViewEntry( const UnoViewSharedPtr& rView ) : + mpView( rView ) + { + } + + /// The view this entry is for + UnoViewSharedPtr mpView; + /// outgoing slide sprite + std::shared_ptr<cppcanvas::CustomSprite> mpOutSprite; + /// incoming slide sprite + std::shared_ptr<cppcanvas::CustomSprite> mpInSprite; + /// outgoing slide bitmap + mutable SlideBitmapSharedPtr mpLeavingBitmap; + /// incoming slide bitmap + mutable SlideBitmapSharedPtr mpEnteringBitmap; + + // for algo access + const UnoViewSharedPtr& getView() const { return mpView; } + }; + + typedef ::std::vector<ViewEntry> ViewsVecT; + + ViewsVecT::const_iterator beginViews() { return maViewData.begin(); } + ViewsVecT::const_iterator endViews() { return maViewData.end(); } + + SlideBitmapSharedPtr getLeavingBitmap( const ViewEntry& rViewEntry ) const; + SlideBitmapSharedPtr getEnteringBitmap( const ViewEntry& rViewEntry ) const; + + SlideBitmapSharedPtr createBitmap( const UnoViewSharedPtr& pView, + const std::optional<SlideSharedPtr>& rSlide_ ) const; + + ::basegfx::B2ISize getEnteringSlideSizePixel( const UnoViewSharedPtr& pView ) const; + + static void renderBitmap( SlideBitmapSharedPtr const& pSlideBitmap, + cppcanvas::CanvasSharedPtr const& pCanvas ); + + /** Called on derived classes to perform actions before first run. + + This typically involves rendering of the initial slide content. + + @param rViewEntry the view entry + + @param rDestinationCanvas the canvas to render on + */ + virtual void prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ); + + /** Called on derived classes to implement actual slide change. + + This method is called with the sprite of the slide coming 'in' + + @param rSprite + Current sprite to operate on. This is the sprite of the + 'entering' slide + + @param t + Current parameter value + */ + virtual void performIn( + const cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ); + + /** Called on derived classes to implement actual slide change. + + This method is called with the sprite of the slide moving 'out' + + @param rSprite + Current sprite to operate on. This is the sprite of the + 'leaving' slide + + @param t + Current parameter value + */ + virtual void performOut( + const cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ); + + ScreenUpdater& getScreenUpdater() const { return mrScreenUpdater; } + +private: + + cppcanvas::CustomSpriteSharedPtr createSprite( + UnoViewSharedPtr const & pView, + ::basegfx::B2DSize const & rSpriteSize, + double nPrio ) const; + + void addSprites( ViewEntry& rEntry ); + static void clearViewEntry( ViewEntry& rEntry ); + + SoundPlayerSharedPtr mpSoundPlayer; + + EventMultiplexer& mrEventMultiplexer; + ScreenUpdater& mrScreenUpdater; + + ::std::optional<SlideSharedPtr> maLeavingSlide; + SlideSharedPtr mpEnteringSlide; + + ViewsVecT maViewData; + const UnoViewContainer& mrViewContainer; + + const bool mbCreateLeavingSprites; + const bool mbCreateEnteringSprites; + bool mbSpritesVisible; + bool mbFinished; + bool mbPrefetched; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SLIDECHANGEBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/slidetransitionfactory.cxx b/slideshow/source/engine/transitions/slidetransitionfactory.cxx new file mode 100644 index 000000000..3ec2c8089 --- /dev/null +++ b/slideshow/source/engine/transitions/slidetransitionfactory.cxx @@ -0,0 +1,1110 @@ +/* -*- 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 <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <cppcanvas/customsprite.hxx> + +#include <com/sun/star/animations/TransitionType.hpp> +#include <com/sun/star/animations/TransitionSubType.hpp> + +#include "slidechangebase.hxx" +#include <transitionfactory.hxx> +#include "transitionfactorytab.hxx" +#include "parametricpolypolygonfactory.hxx" +#include "clippingfunctor.hxx" +#include "combtransition.hxx" +#include <tools.hxx> +#include <memory> + + +/*************************************************** + *** *** + *** Slide Transition Effects *** + *** *** + ***************************************************/ + +using namespace com::sun::star; + +namespace slideshow::internal { + +namespace { + +// helper methods +// ============================================= + +void fillPage( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const ::basegfx::B2DSize& rPageSizePixel, + const RGBColor& rFillColor ) +{ + // need to render without any transformation (we + // assume rPageSizePixel to represent device units) + const ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( + rDestinationCanvas->clone() ); + pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() ); + + // TODO(F2): Properly respect clip here. + // Might have to be transformed, too. + const ::basegfx::B2DHomMatrix aViewTransform( + rDestinationCanvas->getTransformation() ); + const ::basegfx::B2DPoint aOutputPosPixel( + aViewTransform * ::basegfx::B2DPoint() ); + + fillRect( pDevicePixelCanvas, + ::basegfx::B2DRectangle( + aOutputPosPixel.getX(), + aOutputPosPixel.getY(), + aOutputPosPixel.getX() + rPageSizePixel.getX(), + aOutputPosPixel.getY() + rPageSizePixel.getY() ), + rFillColor.getIntegerColor() ); +} + +class PluginSlideChange: public SlideChangeBase +{ + struct TransitionViewPair { + uno::Reference<presentation::XTransition> mxTransition; + UnoViewSharedPtr mpView; + + TransitionViewPair( uno::Reference<presentation::XTransition> const & xTransition, const UnoViewSharedPtr& rView ) + : mxTransition(xTransition), mpView(rView) + { + } + + ~TransitionViewPair() + { + mxTransition.clear(); + mpView.reset(); + } + + void update( double t ) + { + mxTransition->update( t ); + } + }; + +public: + /** Create a new SlideChanger, for the given leaving and + entering slide bitmaps, which uses super secret OpenGL + stuff. + */ + PluginSlideChange( sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType, + const RGBColor& rTransitionFadeColor, + std::optional<SlideSharedPtr> const& leavingSlide_, + const SlideSharedPtr& pEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + const uno::Reference< + presentation::XTransitionFactory>& xFactory, + const SoundPlayerSharedPtr& pSoundPlayer, + EventMultiplexer& rEventMultiplexer) : + SlideChangeBase( leavingSlide_, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ), + maTransitions(), + mbSuccess( false ), + mnTransitionType( nTransitionType ), + mnTransitionSubType( nTransitionSubType ), + mnTransitionFadeColor( rTransitionFadeColor ), + mxFactory( xFactory ) + { + // create one transition per view + for( const auto& rView : rViewContainer ) + { + if( !addTransition( rView ) ) + return; + + ENSURE_OR_THROW(maTransitions.back() && maTransitions.back()->mxTransition.is(), + "Failed to create plugin transition"); + } + mbSuccess = true; + } + + virtual ~PluginSlideChange() override + { + mxFactory.clear(); + } + + bool addTransition( const UnoViewSharedPtr& rView ) + { + uno::Reference<presentation::XTransition> rTransition = mxFactory->createTransition( + mnTransitionType, + mnTransitionSubType, + RGBAColor2UnoColor( mnTransitionFadeColor.getIntegerColor()), + rView->getUnoView(), + getLeavingBitmap(ViewEntry(rView))->getXBitmap(), + getEnteringBitmap(ViewEntry(rView))->getXBitmap() ); + + if( rTransition.is() ) + maTransitions.emplace_back( new TransitionViewPair( rTransition, rView ) ); + else + return false; + + return true; + } + + virtual bool operator()( double t ) override + { + for( const auto& pTransition : maTransitions ) + pTransition->update( t ); + return true; + } + + bool Success() + { + return mbSuccess; + } + + // ViewEventHandler + virtual void viewAdded( const UnoViewSharedPtr& rView ) override + { + SAL_INFO("slideshow", "PluginSlideChange viewAdded"); + SlideChangeBase::viewAdded( rView ); + + for( const auto& pCurrView : maTransitions ) + { + if( pCurrView->mpView == rView ) + return; + } + + SAL_INFO("slideshow", "need to be added" ); + addTransition( rView ); + } + + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override + { + SAL_INFO("slideshow", "PluginSlideChange viewRemoved"); + SlideChangeBase::viewRemoved( rView ); + + auto aIter = std::find_if(maTransitions.begin(), maTransitions.end(), + [&rView](const std::unique_ptr<TransitionViewPair>& rxTransition) { return rxTransition->mpView == rView; }); + if (aIter != maTransitions.end()) + { + SAL_INFO("slideshow", "view removed" ); + maTransitions.erase( aIter ); + } + } + + virtual void viewChanged( const UnoViewSharedPtr& rView ) override + { + SAL_INFO("slideshow", "PluginSlideChange viewChanged"); + SlideChangeBase::viewChanged( rView ); + + for( const auto& pCurrView : maTransitions ) + { + if( pCurrView->mpView == rView ) + { + SAL_INFO("slideshow", "view changed" ); + pCurrView->mxTransition->viewChanged( rView->getUnoView(), + getLeavingBitmap(ViewEntry(rView))->getXBitmap(), + getEnteringBitmap(ViewEntry(rView))->getXBitmap() ); + } + else + SAL_INFO("slideshow", "view did not change" ); + } + } + + virtual void viewsChanged() override + { + SAL_INFO("slideshow", "PluginSlideChange viewsChanged"); + SlideChangeBase::viewsChanged(); + + for( const auto& pCurrView : maTransitions ) + { + SAL_INFO("slideshow", "view changed" ); + UnoViewSharedPtr pView = pCurrView->mpView; + pCurrView->mxTransition->viewChanged( pView->getUnoView(), + getLeavingBitmap(ViewEntry(pView))->getXBitmap(), + getEnteringBitmap(ViewEntry(pView))->getXBitmap() ); + } + } + +private: + // One transition object per view + std::vector< std::unique_ptr<TransitionViewPair> > maTransitions; + + // bool + bool mbSuccess; + + sal_Int16 mnTransitionType; + sal_Int16 mnTransitionSubType; + RGBColor mnTransitionFadeColor; + + uno::Reference<presentation::XTransitionFactory> mxFactory; +}; + +class ClippedSlideChange : public SlideChangeBase +{ +public: + /** Create a new SlideChanger, for the given leaving and + entering slide bitmaps, which applies the given clip + polygon. + */ + ClippedSlideChange( + const SlideSharedPtr& pEnteringSlide, + const ParametricPolyPolygonSharedPtr& rPolygon, + const TransitionInfo& rTransitionInfo, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + bool bDirectionForward, + const SoundPlayerSharedPtr& pSoundPlayer ) : + SlideChangeBase( + // leaving bitmap is empty, we're leveraging the fact that the + // old slide is still displayed in the background: + std::optional<SlideSharedPtr>(), + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ), + maClippingFunctor( rPolygon, + rTransitionInfo, + bDirectionForward, + true ) + {} + + virtual void performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + + virtual void performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + +private: + ClippingFunctor maClippingFunctor; +}; + +void ClippedSlideChange::performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double t ) +{ + // #i46602# Better work in device coordinate space here, + // otherwise, we too easily suffer from roundoffs. Apart from + // that, getEnteringSizePixel() _guarantees_ to cover the whole + // slide bitmap. There's a catch, though: this removes any effect + // of the view transformation (e.g. rotation) from the transition. + rSprite->setClipPixel( + maClippingFunctor( t, + ::basegfx::B2DSize( getEnteringSlideSizePixel(rViewEntry.mpView) ) ) ); +} + +void ClippedSlideChange::performOut( + const ::cppcanvas::CustomSpriteSharedPtr& /*rSprite*/, + const ViewEntry& /*rViewEntry*/, + const ::cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double /*t*/ ) +{ + // not needed here +} + + +class FadingSlideChange : public SlideChangeBase +{ +public: + /** Create a new SlideChanger, for the given leaving and + entering slides, which applies a fade effect. + */ + FadingSlideChange( + std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + std::optional<RGBColor> const& rFadeColor, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer ) + : SlideChangeBase( leavingSlide, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ), + maFadeColor( rFadeColor ) + {} + + virtual void prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) override; + + virtual void performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + + virtual void performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + +private: + const std::optional< RGBColor > maFadeColor; +}; + +void FadingSlideChange::prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) +{ + if ( maFadeColor ) + { + // clear page to given fade color. 'Leaving' slide is + // painted atop of that, but slowly fading out. + fillPage( rDestinationCanvas, + ::basegfx::B2DSize( getEnteringSlideSizePixel( rViewEntry.mpView ) ), + *maFadeColor ); + } +} + +void FadingSlideChange::performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& /*rViewEntry*/, + const ::cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double t ) +{ + ENSURE_OR_THROW( + rSprite, + "FadingSlideChange::performIn(): Invalid sprite" ); + + if( maFadeColor ) + // After half of the active time, fade in new slide + rSprite->setAlpha( t > 0.5 ? 2.0*(t-0.5) : 0.0 ); + else + // Fade in new slide over full active time + rSprite->setAlpha( t ); +} + +void FadingSlideChange::performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& /* rViewEntry */, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) +{ + ENSURE_OR_THROW( + rSprite, + "FadingSlideChange::performOut(): Invalid sprite" ); + ENSURE_OR_THROW( + rDestinationCanvas, + "FadingSlideChange::performOut(): Invalid dest canvas" ); + + // only needed for color fades + if( maFadeColor ) + { + // Until half of the active time, fade out old + // slide. After half of the active time, old slide + // will be invisible. + rSprite->setAlpha( t > 0.5 ? 0.0 : 2.0*(0.5-t) ); + } +} + +class CutSlideChange : public SlideChangeBase +{ +public: + /** Create a new SlideChanger, for the given leaving and + entering slides, which applies a cut effect. + */ + CutSlideChange( + std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const RGBColor& rFadeColor, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer ) + : SlideChangeBase( leavingSlide, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ), + maFadeColor( rFadeColor ) + {} + + virtual void prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) override; + + virtual void performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + + virtual void performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + +private: + RGBColor maFadeColor; +}; + +void CutSlideChange::prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) +{ + // clear page to given fade color. 'Leaving' slide is + // painted atop of that + fillPage( rDestinationCanvas, + ::basegfx::B2DSize( getEnteringSlideSizePixel( rViewEntry.mpView ) ), + maFadeColor ); +} + +void CutSlideChange::performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& /*rViewEntry*/, + const ::cppcanvas::CanvasSharedPtr& /*rDestinationCanvas*/, + double t ) +{ + ENSURE_OR_THROW( + rSprite, + "CutSlideChange::performIn(): Invalid sprite" ); + + // After 2/3rd of the active time, display new slide + rSprite->setAlpha( t > 2/3.0 ? 1.0 : 0.0 ); +} + +void CutSlideChange::performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& /* rViewEntry */, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) +{ + ENSURE_OR_THROW( + rSprite, + "CutSlideChange::performOut(): Invalid sprite" ); + ENSURE_OR_THROW( + rDestinationCanvas, + "CutSlideChange::performOut(): Invalid dest canvas" ); + + // Until 1/3rd of the active time, display old slide. + rSprite->setAlpha( t > 1/3.0 ? 0.0 : 1.0 ); +} + +class MovingSlideChange : public SlideChangeBase +{ + /// Direction vector for leaving slide, + const ::basegfx::B2DVector maLeavingDirection; + + /// Direction vector for entering slide, + const ::basegfx::B2DVector maEnteringDirection; + +public: + /** Create a new SlideChanger, for the given entering slide + bitmaps, which performs a moving slide change effect + + @param rLeavingDirection + Direction vector. The move is performed along this + direction vector, starting at a position where the leaving + slide is fully visible, and ending at a position where the + leaving slide is just not visible. The vector must have + unit length. + + @param rEnteringDirection + Direction vector. The move is performed along this + direction vector, starting at a position where the + entering slide is just not visible, and ending at the + final slide position. The vector must have unit length. + */ + MovingSlideChange( + const std::optional<SlideSharedPtr>& leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const SoundPlayerSharedPtr& pSoundPlayer, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const ::basegfx::B2DVector& rLeavingDirection, + const ::basegfx::B2DVector& rEnteringDirection ) + : SlideChangeBase( + leavingSlide, pEnteringSlide, pSoundPlayer, + rViewContainer, rScreenUpdater, rEventMultiplexer, + // Optimization: when leaving bitmap is given, + // but it does not move, don't create sprites for it, + // we simply paint it once at startup: + !rLeavingDirection.equalZero() /* bCreateLeavingSprites */, + !rEnteringDirection.equalZero() /* bCreateEnteringSprites */ ), + // TODO(F1): calc correct length of direction + // vector. Directions not strictly horizontal or vertical + // must travel a longer distance. + maLeavingDirection( rLeavingDirection ), + // TODO(F1): calc correct length of direction + // vector. Directions not strictly horizontal or vertical + // must travel a longer distance. + maEnteringDirection( rEnteringDirection ) + {} + + virtual void prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) override; + + virtual void performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; + + virtual void performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) override; +}; + +void MovingSlideChange::prepareForRun( + const ViewEntry& rViewEntry, + const cppcanvas::CanvasSharedPtr& rDestinationCanvas ) +{ + if ( maLeavingDirection.equalZero() ) + renderBitmap( getLeavingBitmap( rViewEntry ), rDestinationCanvas ); + else if ( maEnteringDirection.equalZero() ) + renderBitmap( getEnteringBitmap( rViewEntry ), rDestinationCanvas ); +} + +void MovingSlideChange::performIn( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) +{ + // intro sprite moves: + + ENSURE_OR_THROW( + rSprite, + "MovingSlideChange::performIn(): Invalid sprite" ); + ENSURE_OR_THROW( + rDestinationCanvas, + "MovingSlideChange::performIn(): Invalid dest canvas" ); + + // TODO(F1): This does not account for non-translational + // transformations! If the canvas is rotated, we still + // move the sprite unrotated (which might or might not + // produce the intended effect). + const basegfx::B2DHomMatrix aViewTransform( + rDestinationCanvas->getTransformation() ); + const basegfx::B2DPoint aPageOrigin( + aViewTransform * basegfx::B2DPoint() ); + + // move sprite + rSprite->movePixel( + aPageOrigin + + ((t - 1.0) * + ::basegfx::B2DSize( getEnteringSlideSizePixel(rViewEntry.mpView) ) * + maEnteringDirection) ); +} + +void MovingSlideChange::performOut( + const ::cppcanvas::CustomSpriteSharedPtr& rSprite, + const ViewEntry& rViewEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + double t ) +{ + // outro sprite moves: + + ENSURE_OR_THROW( + rSprite, + "MovingSlideChange::performOut(): Invalid sprite" ); + ENSURE_OR_THROW( + rDestinationCanvas, + "MovingSlideChange::performOut(): Invalid dest canvas" ); + + // TODO(F1): This does not account for non-translational + // transformations! If the canvas is rotated, we still + // move the sprite unrotated (which might or might not + // produce the intended effect). + const basegfx::B2DHomMatrix aViewTransform( + rDestinationCanvas->getTransformation() ); + const basegfx::B2DPoint aPageOrigin( + aViewTransform * basegfx::B2DPoint() ); + + // move sprite + rSprite->movePixel( + aPageOrigin + (t * + ::basegfx::B2DSize( getEnteringSlideSizePixel(rViewEntry.mpView) ) * + maLeavingDirection) ); +} + + +NumberAnimationSharedPtr createPushWipeTransition( + std::optional<SlideSharedPtr> const & leavingSlide_, + const SlideSharedPtr& pEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + sal_Int16 /*nTransitionType*/, + sal_Int16 nTransitionSubType, + bool /*bTransitionDirection*/, + const SoundPlayerSharedPtr& pSoundPlayer ) +{ + std::optional<SlideSharedPtr> leavingSlide; // no bitmap + if (leavingSlide_ && *leavingSlide_ != nullptr) + { + // opt: only page, if we've an + // actual slide to move out here. We + // _don't_ need a fake black background + // bitmap, neither for push nor for comb + // wipes. + leavingSlide = leavingSlide_; + } + + // setup direction vector + bool bComb( false ); + ::basegfx::B2DVector aDirection; + switch( nTransitionSubType ) + { + default: + OSL_FAIL( + "createPushWipeTransition(): Unexpected transition " + "subtype for animations::TransitionType::PUSHWIPE " + "transitions" ); + return NumberAnimationSharedPtr(); + + case animations::TransitionSubType::FROMTOP: + aDirection = ::basegfx::B2DVector( 0.0, 1.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOM: + aDirection = ::basegfx::B2DVector( 0.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMLEFT: + aDirection = ::basegfx::B2DVector( 1.0, 0.0 ); + break; + + case animations::TransitionSubType::FROMRIGHT: + aDirection = ::basegfx::B2DVector( -1.0, 0.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOMRIGHT: + aDirection = ::basegfx::B2DVector( -1.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOMLEFT: + aDirection = ::basegfx::B2DVector( 1.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMTOPRIGHT: + aDirection = ::basegfx::B2DVector( -1.0, 1.0 ); + break; + + case animations::TransitionSubType::FROMTOPLEFT: + aDirection = ::basegfx::B2DVector( 1.0, 1.0 ); + break; + + case animations::TransitionSubType::COMBHORIZONTAL: + aDirection = ::basegfx::B2DVector( 1.0, 0.0 ); + bComb = true; + break; + + case animations::TransitionSubType::COMBVERTICAL: + aDirection = ::basegfx::B2DVector( 0.0, 1.0 ); + bComb = true; + break; + } + + if( bComb ) + { + return std::make_shared<CombTransition>( leavingSlide, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + aDirection, + 24 /* comb with 12 stripes */ ); + } + else + { + return std::make_shared<MovingSlideChange>( leavingSlide, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + aDirection, + aDirection ); + } +} + +NumberAnimationSharedPtr createSlideWipeTransition( + std::optional<SlideSharedPtr> const & leavingSlide, + const SlideSharedPtr& pEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + sal_Int16 /*nTransitionType*/, + sal_Int16 nTransitionSubType, + bool bTransitionDirection, + const SoundPlayerSharedPtr& pSoundPlayer ) +{ + // setup 'in' direction vector + ::basegfx::B2DVector aInDirection; + switch( nTransitionSubType ) + { + default: + OSL_FAIL( + "createSlideWipeTransition(): Unexpected transition " + "subtype for animations::TransitionType::SLIDEWIPE " + "transitions" ); + return NumberAnimationSharedPtr(); + + case animations::TransitionSubType::FROMTOP: + aInDirection = ::basegfx::B2DVector( 0.0, 1.0 ); + break; + + case animations::TransitionSubType::FROMRIGHT: + aInDirection = ::basegfx::B2DVector( -1.0, 0.0 ); + break; + + case animations::TransitionSubType::FROMLEFT: + aInDirection = ::basegfx::B2DVector( 1.0, 0.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOM: + aInDirection = ::basegfx::B2DVector( 0.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOMRIGHT: + aInDirection = ::basegfx::B2DVector( -1.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMBOTTOMLEFT: + aInDirection = ::basegfx::B2DVector( 1.0, -1.0 ); + break; + + case animations::TransitionSubType::FROMTOPRIGHT: + aInDirection = ::basegfx::B2DVector( -1.0, 1.0 ); + break; + + case animations::TransitionSubType::FROMTOPLEFT: + aInDirection = ::basegfx::B2DVector( 1.0, 1.0 ); + break; + } + + if( bTransitionDirection ) + { + // normal, 'forward' slide wipe effect. Since the old + // content is still on screen (and does not move), we omit + // the 'leaving' slide. + + + return std::make_shared<MovingSlideChange>( + std::optional<SlideSharedPtr>() /* no slide */, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + basegfx::B2DVector(), + aInDirection ); + } + else + { + // 'reversed' slide wipe effect. Reverse for slide wipes + // means, that the new slide is in the back, statically, + // and the old one is moving off in the foreground. + + + return std::make_shared<MovingSlideChange>( leavingSlide, + pEnteringSlide, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + aInDirection, + basegfx::B2DVector() ); + } +} + +NumberAnimationSharedPtr createPluginTransition( + sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType, + const RGBColor& rTransitionFadeColor, + std::optional<SlideSharedPtr> const& pLeavingSlide, + const SlideSharedPtr& pEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + const uno::Reference< + presentation::XTransitionFactory>& xFactory, + const SoundPlayerSharedPtr& pSoundPlayer, + EventMultiplexer& rEventMultiplexer) +{ + auto pTransition = + std::make_shared<PluginSlideChange>( + nTransitionType, + nTransitionSubType, + rTransitionFadeColor, + pLeavingSlide, + pEnteringSlide, + rViewContainer, + rScreenUpdater, + xFactory, + pSoundPlayer, + rEventMultiplexer ); + + if( !pTransition->Success() ) + return nullptr; + return pTransition; +} + +} // anon namespace + + +NumberAnimationSharedPtr TransitionFactory::createSlideTransition( + const SlideSharedPtr& pLeavingSlide, + const SlideSharedPtr& pEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const uno::Reference<presentation::XTransitionFactory>& xOptionalFactory, + sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType, + bool bTransitionDirection, + const RGBColor& rTransitionFadeColor, + const SoundPlayerSharedPtr& pSoundPlayer ) +{ + // xxx todo: change to TransitionType::NONE, TransitionSubType::NONE: + if (nTransitionType == 0 && nTransitionSubType == 0) { + // just play sound, no slide transition: + if (pSoundPlayer) { + pSoundPlayer->startPlayback(); + // xxx todo: for now, presentation.cxx takes care about the slide + // #i50492# transition sound object, so just release it here + } + return NumberAnimationSharedPtr(); + } + + ENSURE_OR_THROW( + pEnteringSlide, + "TransitionFactory::createSlideTransition(): Invalid entering slide" ); + + if( xOptionalFactory.is() && + xOptionalFactory->hasTransition(nTransitionType, nTransitionSubType) ) + { + // #i82460# - optional plugin factory claims this transition. delegate. + NumberAnimationSharedPtr pTransition( + createPluginTransition( + nTransitionType, + nTransitionSubType, + rTransitionFadeColor, + std::make_optional(pLeavingSlide), + pEnteringSlide, + rViewContainer, + rScreenUpdater, + xOptionalFactory, + pSoundPlayer, + rEventMultiplexer )); + + if( pTransition ) + return pTransition; + } + + const TransitionInfo* pTransitionInfo( + getTransitionInfo( nTransitionType, nTransitionSubType ) ); + + if( pTransitionInfo != nullptr ) + { + switch( pTransitionInfo->meTransitionClass ) + { + default: + case TransitionInfo::TRANSITION_INVALID: + SAL_WARN("slideshow", + "TransitionFactory::createSlideTransition(): " + "Invalid type/subtype combination encountered." + << nTransitionType << " " << nTransitionSubType ); + return NumberAnimationSharedPtr(); + + + case TransitionInfo::TRANSITION_CLIP_POLYPOLYGON: + { + // generate parametric poly-polygon + ParametricPolyPolygonSharedPtr pPoly( + ParametricPolyPolygonFactory::createClipPolyPolygon( + nTransitionType, nTransitionSubType ) ); + + // create a clip transition from that + return std::make_shared<ClippedSlideChange>( pEnteringSlide, + pPoly, + *pTransitionInfo, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + bTransitionDirection, + pSoundPlayer ); + } + + case TransitionInfo::TRANSITION_SPECIAL: + { + switch( nTransitionType ) + { + default: + OSL_FAIL( + "TransitionFactory::createSlideTransition(): " + "Unexpected transition type for " + "TRANSITION_SPECIAL transitions" ); + return NumberAnimationSharedPtr(); + + case animations::TransitionType::RANDOM: + { + // select randomly one of the effects from the + // TransitionFactoryTable + + const TransitionInfo* pRandomTransitionInfo( + getRandomTransitionInfo() ); + + ENSURE_OR_THROW( + pRandomTransitionInfo != nullptr, + "TransitionFactory::createSlideTransition(): " + "Got invalid random transition info" ); + + ENSURE_OR_THROW( + pRandomTransitionInfo->mnTransitionType != + animations::TransitionType::RANDOM, + "TransitionFactory::createSlideTransition(): " + "Got random again for random input!" ); + + // and recurse + return createSlideTransition( + pLeavingSlide, + pEnteringSlide, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + xOptionalFactory, + pRandomTransitionInfo->mnTransitionType, + pRandomTransitionInfo->mnTransitionSubType, + bTransitionDirection, + rTransitionFadeColor, + pSoundPlayer ); + } + + case animations::TransitionType::PUSHWIPE: + { + return createPushWipeTransition( + std::make_optional(pLeavingSlide), + pEnteringSlide, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + nTransitionType, + nTransitionSubType, + bTransitionDirection, + pSoundPlayer ); + } + + case animations::TransitionType::SLIDEWIPE: + { + return createSlideWipeTransition( + std::make_optional(pLeavingSlide), + pEnteringSlide, + rViewContainer, + rScreenUpdater, + rEventMultiplexer, + nTransitionType, + nTransitionSubType, + bTransitionDirection, + pSoundPlayer ); + } + + case animations::TransitionType::BARWIPE: + case animations::TransitionType::FADE: + { + // black page: + std::optional<SlideSharedPtr> leavingSlide; + std::optional<RGBColor> aFadeColor; + + switch( nTransitionSubType ) + { + case animations::TransitionSubType::CROSSFADE: + // crossfade needs no further setup, + // just blend new slide over current + // slide. + break; + + // TODO(F1): Implement toColor/fromColor fades + case animations::TransitionSubType::FADETOCOLOR: + case animations::TransitionSubType::FADEFROMCOLOR: + case animations::TransitionSubType::FADEOVERCOLOR: + if (pLeavingSlide) { + // only generate, if fade + // effect really needs it. + leavingSlide = pLeavingSlide; + } + aFadeColor = rTransitionFadeColor; + break; + + default: + ENSURE_OR_THROW( false, + "SlideTransitionFactory::createSlideTransition(): Unknown FADE subtype" ); + } + + if( nTransitionType == animations::TransitionType::FADE ) + return std::make_shared<FadingSlideChange>( + leavingSlide, + pEnteringSlide, + aFadeColor, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ); + else + return std::make_shared<CutSlideChange>( + leavingSlide, + pEnteringSlide, + rTransitionFadeColor, + pSoundPlayer, + rViewContainer, + rScreenUpdater, + rEventMultiplexer ); + } + } + } + break; + } + } + + // No animation generated, maybe no table entry for given + // transition? + SAL_WARN("slideshow", + "TransitionFactory::createSlideTransition(): " + "Unknown type/subtype combination encountered " + << nTransitionType << " " << nTransitionSubType ); + OSL_FAIL( + "TransitionFactory::createSlideTransition(): " + "Unknown type/subtype combination encountered" ); + + return NumberAnimationSharedPtr(); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/snakewipe.cxx b/slideshow/source/engine/transitions/snakewipe.cxx new file mode 100644 index 000000000..ceaa1d001 --- /dev/null +++ b/slideshow/source/engine/transitions/snakewipe.cxx @@ -0,0 +1,238 @@ +/* -*- 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 <cmath> + +#include <o3tl/temporary.hxx> +#include <osl/diagnose.h> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "snakewipe.hxx" +#include "transitiontools.hxx" + + +namespace slideshow::internal { + +SnakeWipe::SnakeWipe( sal_Int32 nElements, bool diagonal, bool flipOnYAxis ) + : m_sqrtElements( static_cast<sal_Int32>( + sqrt( static_cast<double>(nElements) ) ) ), + m_elementEdge( 1.0 / m_sqrtElements ), + m_diagonal(diagonal), + m_flipOnYAxis(flipOnYAxis) +{ +} + +::basegfx::B2DPolyPolygon SnakeWipe::calcSnake( double t ) const +{ + ::basegfx::B2DPolyPolygon res; + const double area = t * m_sqrtElements * m_sqrtElements; + const sal_Int32 line_ = static_cast<sal_Int32>(area) / m_sqrtElements; + const double line = ::basegfx::pruneScaleValue( + static_cast<double>(line_) / m_sqrtElements ); + const double col = ::basegfx::pruneScaleValue( + (area - (line_ * m_sqrtElements)) / m_sqrtElements ); + + if (! ::basegfx::fTools::equalZero( line )) { + ::basegfx::B2DPolygon poly; + poly.append( ::basegfx::B2DPoint( 0.0, 0.0 ) ); + poly.append( ::basegfx::B2DPoint( 0.0, line ) ); + poly.append( ::basegfx::B2DPoint( 1.0, line ) ); + poly.append( ::basegfx::B2DPoint( 1.0, 0.0 ) ); + poly.setClosed(true); + res.append(poly); + } + if (! ::basegfx::fTools::equalZero( col )) + { + double offset = 0.0; + if ((line_ & 1) == 1) { + // odd line: => right to left + offset = (1.0 - col); + } + ::basegfx::B2DPolygon poly; + poly.append( ::basegfx::B2DPoint( offset, line ) ); + poly.append( ::basegfx::B2DPoint( offset, + line + m_elementEdge ) ); + poly.append( ::basegfx::B2DPoint( offset + col, + line + m_elementEdge ) ); + poly.append( ::basegfx::B2DPoint( offset + col, line ) ); + poly.setClosed(true); + res.append(poly); + } + + return res; +} + +::basegfx::B2DPolyPolygon SnakeWipe::calcHalfDiagonalSnake( + double t, bool in ) const +{ + ::basegfx::B2DPolyPolygon res; + + if (in) { + const double sqrtArea2 = sqrt( t * m_sqrtElements * m_sqrtElements ); + const double edge = ::basegfx::pruneScaleValue( + std::trunc(sqrtArea2) / + m_sqrtElements ); + + ::basegfx::B2DPolygon poly; + if (! ::basegfx::fTools::equalZero( edge )) { + poly.append( ::basegfx::B2DPoint( 0.0, 0.0 ) ); + poly.append( ::basegfx::B2DPoint( 0.0, edge ) ); + poly.append( ::basegfx::B2DPoint( edge, 0.0 ) ); + poly.setClosed(true); + res.append(poly); + } + const double a = M_SQRT1_2 / m_sqrtElements; + const double d = std::modf(sqrtArea2, &o3tl::temporary(double())); + const double len = t * M_SQRT2 * d; + const double height = ::basegfx::pruneScaleValue( M_SQRT1_2 / m_sqrtElements ); + poly.clear(); + poly.append( ::basegfx::B2DPoint( 0.0, 0.0 ) ); + poly.append( ::basegfx::B2DPoint( 0.0, height ) ); + poly.append( ::basegfx::B2DPoint( len + a, height ) ); + poly.append( ::basegfx::B2DPoint( len + a, 0.0 ) ); + poly.setClosed(true); + ::basegfx::B2DHomMatrix aTransform; + + if ((static_cast<sal_Int32>(sqrtArea2) & 1) == 1) + { + // odd line + aTransform = basegfx::utils::createRotateB2DHomMatrix(M_PI_2 + M_PI_4); + aTransform.translate(edge + m_elementEdge, 0.0); + } + else + { + aTransform = basegfx::utils::createTranslateB2DHomMatrix(-a, 0.0); + aTransform.rotate( -M_PI_4 ); + aTransform.translate( 0.0, edge ); + } + + poly.transform( aTransform ); + res.append(poly); + } + else // out + { + const double sqrtArea2 = sqrt( t * m_sqrtElements * m_sqrtElements ); + const double edge = ::basegfx::pruneScaleValue( + std::trunc(sqrtArea2) / + m_sqrtElements ); + + ::basegfx::B2DPolygon poly; + if (! ::basegfx::fTools::equalZero( edge )) { + poly.append( ::basegfx::B2DPoint( 0.0, 1.0 ) ); + poly.append( ::basegfx::B2DPoint( edge, 1.0 ) ); + poly.append( ::basegfx::B2DPoint( 1.0, edge ) ); + poly.append( ::basegfx::B2DPoint( 1.0, 0.0 ) ); + poly.setClosed(true); + res.append(poly); + } + const double a = M_SQRT1_2 / m_sqrtElements; + const double d = std::modf(sqrtArea2, &o3tl::temporary(double())); + const double len = (1.0 - t) * M_SQRT2 * d; + const double height = ::basegfx::pruneScaleValue( M_SQRT1_2 / m_sqrtElements ); + poly.clear(); + poly.append( ::basegfx::B2DPoint( 0.0, 0.0 ) ); + poly.append( ::basegfx::B2DPoint( 0.0, height ) ); + poly.append( ::basegfx::B2DPoint( len + a, height ) ); + poly.append( ::basegfx::B2DPoint( len + a, 0.0 ) ); + poly.setClosed(true); + ::basegfx::B2DHomMatrix aTransform; + + if ((static_cast<sal_Int32>(sqrtArea2) & 1) == 1) + { + // odd line + aTransform = basegfx::utils::createTranslateB2DHomMatrix(0.0, -height); + aTransform.rotate( M_PI_2 + M_PI_4 ); + aTransform.translate( 1.0, edge ); + } + else + { + aTransform = basegfx::utils::createRotateB2DHomMatrix(-M_PI_4); + aTransform.translate( edge, 1.0 ); + } + poly.transform( aTransform ); + res.append(poly); + } + + return res; +} + +::basegfx::B2DPolyPolygon SnakeWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res; + if (m_diagonal) + { + if (t >= 0.5) { + res.append( calcHalfDiagonalSnake( 1.0, true ) ); + res.append( calcHalfDiagonalSnake( 2.0 * (t - 0.5), false ) ); + } + else + res.append( calcHalfDiagonalSnake( 2.0 * t, true ) ); + } + else + res = calcSnake(t); + + return m_flipOnYAxis ? flipOnYAxis(res) : res; +} + +::basegfx::B2DPolyPolygon ParallelSnakesWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res; + if (m_diagonal) + { + OSL_ASSERT( m_opposite ); + ::basegfx::B2DPolyPolygon half( + calcHalfDiagonalSnake( t, false /* out */ ) ); + // flip on x axis and rotate 90 degrees: + basegfx::B2DHomMatrix aTransform(basegfx::utils::createScaleB2DHomMatrix(1.0, -1.0)); + aTransform.translate( -0.5, 0.5 ); + aTransform.rotate( M_PI_2 ); + aTransform.translate( 0.5, 0.5 ); + half.transform( aTransform ); + half.flip(); + res.append( half ); + + // rotate 180 degrees: + aTransform = basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5); + aTransform.rotate( M_PI ); + aTransform.translate( 0.5, 0.5 ); + half.transform( aTransform ); + res.append( half ); + } + else + { + ::basegfx::B2DPolyPolygon half( calcSnake( t / 2.0 ) ); + // rotate 90 degrees: + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5)); + aTransform.rotate( M_PI_2 ); + aTransform.translate( 0.5, 0.5 ); + half.transform( aTransform ); + res.append( flipOnYAxis(half) ); + res.append( m_opposite ? flipOnXAxis(half) : half ); + } + + return m_flipOnYAxis ? flipOnYAxis(res) : res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/snakewipe.hxx b/slideshow/source/engine/transitions/snakewipe.hxx new file mode 100644 index 000000000..3da6918b9 --- /dev/null +++ b/slideshow/source/engine/transitions/snakewipe.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_TRANSITIONS_SNAKEWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SNAKEWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generates a snake wipe: +class SnakeWipe : public ParametricPolyPolygon +{ +public: + SnakeWipe( sal_Int32 nElements, bool diagonal, bool flipOnYAxis ); + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; + +protected: + // topLeftHorizontal: + ::basegfx::B2DPolyPolygon calcSnake( double t ) const; + // topLeftDiagonal: + ::basegfx::B2DPolyPolygon calcHalfDiagonalSnake( double t, bool in ) const; + + const sal_Int32 m_sqrtElements; + const double m_elementEdge; + const bool m_diagonal; + const bool m_flipOnYAxis; +}; + +/// Generates a parallel snakes wipe: +class ParallelSnakesWipe : public SnakeWipe +{ +public: + ParallelSnakesWipe( sal_Int32 nElements, + bool diagonal, bool flipOnYAxis, bool opposite ) + : SnakeWipe( nElements, diagonal, flipOnYAxis ), + m_opposite( opposite ) + {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const bool m_opposite; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SNAKEWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/spiralwipe.cxx b/slideshow/source/engine/transitions/spiralwipe.cxx new file mode 100644 index 000000000..3d1dc0282 --- /dev/null +++ b/slideshow/source/engine/transitions/spiralwipe.cxx @@ -0,0 +1,120 @@ +/* -*- 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 "spiralwipe.hxx" +#include "transitiontools.hxx" + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +namespace slideshow::internal { + +SpiralWipe::SpiralWipe( sal_Int32 nElements, bool flipOnYAxis ) + : m_elements(nElements), + m_sqrtElements( static_cast<sal_Int32>( + sqrt( static_cast<double>(nElements) ) ) ), + m_flipOnYAxis(flipOnYAxis) +{ +} + +::basegfx::B2DPolyPolygon SpiralWipe::calcNegSpiral( double t ) const +{ + const double area = t * m_elements; + const double e = sqrt(area) / 2.0; + const sal_Int32 edge = static_cast<sal_Int32>(e) * 2; + + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5)); + const double edge_ = ::basegfx::pruneScaleValue( + static_cast<double>(edge) / m_sqrtElements ); + aTransform.scale( edge_, edge_ ); + aTransform.translate( 0.5, 0.5 ); + ::basegfx::B2DPolygon poly( createUnitRect() ); + poly.transform( aTransform ); + ::basegfx::B2DPolyPolygon res(poly); + + if (! ::basegfx::fTools::equalZero( 1.0 - t )) { + const sal_Int32 edge1 = edge + 1; + sal_Int32 len = static_cast<sal_Int32>( (e - (edge /2)) * edge1 * 4 ); + double w = M_PI_2; + while (len > 0) { + const sal_Int32 alen = std::min(len, edge1); + len -= alen; + poly = createUnitRect(); + aTransform = basegfx::utils::createScaleB2DHomMatrix( + ::basegfx::pruneScaleValue( static_cast<double>(alen) / m_sqrtElements ), + ::basegfx::pruneScaleValue( 1.0 / m_sqrtElements ) ); + aTransform.translate( + - ::basegfx::pruneScaleValue( + static_cast<double>(edge / 2) / m_sqrtElements ), + ::basegfx::pruneScaleValue( + static_cast<double>(edge / 2) / m_sqrtElements ) ); + aTransform.rotate( w ); + w -= M_PI_2; + aTransform.translate( 0.5, 0.5 ); + poly.transform( aTransform ); + res.append(poly); + } + } + + return res; +} + +::basegfx::B2DPolyPolygon SpiralWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res( createUnitRect() ); + ::basegfx::B2DPolyPolygon innerSpiral( calcNegSpiral( 1.0 - t ) ); + innerSpiral.flip(); + res.append(innerSpiral); + return m_flipOnYAxis ? flipOnYAxis(res) : res; +} + +::basegfx::B2DPolyPolygon BoxSnakesWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res( createUnitRect() ); + ::basegfx::B2DPolyPolygon innerSpiral( calcNegSpiral( 1.0 - t ) ); + innerSpiral.flip(); + + if (m_fourBox) { + ::basegfx::B2DHomMatrix aTransform; + aTransform.scale( 0.5, 0.5 ); + innerSpiral.transform( aTransform ); + res.append(innerSpiral); + res.append( flipOnXAxis(innerSpiral) ); + innerSpiral = flipOnYAxis(innerSpiral); + res.append(innerSpiral); + res.append( flipOnXAxis(innerSpiral) ); + } + else { + ::basegfx::B2DHomMatrix aTransform; + aTransform.scale( 1.0, 0.5 ); + innerSpiral.transform( aTransform ); + res.append(innerSpiral); + res.append( flipOnXAxis(innerSpiral) ); + } + + return m_flipOnYAxis ? flipOnYAxis(res) : res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/spiralwipe.hxx b/slideshow/source/engine/transitions/spiralwipe.hxx new file mode 100644 index 000000000..73f6a59c5 --- /dev/null +++ b/slideshow/source/engine/transitions/spiralwipe.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SPIRALWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SPIRALWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +/// Generates a topLeftClockWise or +/// bottomLeftCounterClockWise (flipOnYAxis=true) spiral wipe: +class SpiralWipe : public ParametricPolyPolygon +{ +public: + SpiralWipe( sal_Int32 nElements, bool flipOnYAxis = false ); + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +protected: + ::basegfx::B2DPolyPolygon calcNegSpiral( double t ) const; + + const sal_Int32 m_elements; + const sal_Int32 m_sqrtElements; + const bool m_flipOnYAxis; +}; + +/// Generates a twoBoxLeft or fourBoxHorizontal wipe: +class BoxSnakesWipe : public SpiralWipe +{ +public: + BoxSnakesWipe( sal_Int32 nElements, bool fourBox ) + : SpiralWipe(nElements), m_fourBox(fourBox) {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const bool m_fourBox; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SPIRALWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/sweepwipe.cxx b/slideshow/source/engine/transitions/sweepwipe.cxx new file mode 100644 index 000000000..e6fcb8c2d --- /dev/null +++ b/slideshow/source/engine/transitions/sweepwipe.cxx @@ -0,0 +1,72 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "clockwipe.hxx" +#include "sweepwipe.hxx" +#include "transitiontools.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon SweepWipe::operator () ( double t ) +{ + t /= 2.0; + if (! m_center) + t /= 2.0; + if (!m_single && !m_oppositeVertical) + t /= 2.0; + + ::basegfx::B2DPolygon poly( ClockWipe::calcCenteredClock( 0.25 + t ) ); + ::basegfx::B2DHomMatrix aTransform; + + if (m_center) + { + aTransform = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.0); + poly.transform( aTransform ); + } + ::basegfx::B2DPolyPolygon res(poly); + + if (! m_single) + { + if (m_oppositeVertical) + { + aTransform = basegfx::utils::createScaleB2DHomMatrix(1.0, -1.0); + aTransform.translate( 0.0, 1.0 ); + poly.transform( aTransform ); + poly.flip(); + } + else + { + aTransform = basegfx::utils::createTranslateB2DHomMatrix(-0.5, -0.5); + aTransform.rotate( M_PI ); + aTransform.translate( 0.5, 0.5 ); + poly.transform( aTransform ); + } + res.append(poly); + } + + return m_flipOnYAxis ? flipOnYAxis(res) : res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/sweepwipe.hxx b/slideshow/source/engine/transitions/sweepwipe.hxx new file mode 100644 index 000000000..e010a5543 --- /dev/null +++ b/slideshow/source/engine/transitions/sweepwipe.hxx @@ -0,0 +1,46 @@ +/* -*- 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_TRANSITIONS_SWEEPWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SWEEPWIPE_HXX + +#include "parametricpolypolygon.hxx" + + +namespace slideshow::internal { + +class SweepWipe : public ParametricPolyPolygon +{ +public: + SweepWipe( bool center, bool single, + bool oppositeVertical, bool flipOnYAxis ) + : m_center(center), m_single(single), + m_oppositeVertical(oppositeVertical), m_flipOnYAxis(flipOnYAxis) + {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + const bool m_center, m_single, m_oppositeVertical, m_flipOnYAxis; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_SWEEPWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/transitionfactorytab.cxx b/slideshow/source/engine/transitions/transitionfactorytab.cxx new file mode 100644 index 000000000..6a22fdd0a --- /dev/null +++ b/slideshow/source/engine/transitions/transitionfactorytab.cxx @@ -0,0 +1,2134 @@ +/* -*- 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/TransitionType.hpp> +#include <com/sun/star/animations/TransitionSubType.hpp> + +#include "transitionfactorytab.hxx" +#include <transitioninfo.hxx> +#include <tools.hxx> + +#include <algorithm> + +using namespace ::com::sun::star; + +namespace slideshow::internal { + +namespace { + +const TransitionInfo lcl_transitionInfo[] = +{ + { + 0, + 0, + TransitionInfo::TRANSITION_INVALID, + 0.0, + 0.0, + 0.0, + TransitionInfo::ReverseMethod::Ignore, + false, + false + }, + { + // mapped to BarWipePolyPolygon: + animations::TransitionType::BARWIPE, + animations::TransitionSubType::LEFTTORIGHT, // (1) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + false, // 'out' by subtraction + false // scale isotropically to target size + }, + { + // mapped to BarWipePolyPolygon: + animations::TransitionType::BARWIPE, + animations::TransitionSubType::TOPTOBOTTOM, // (2) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + false, // 'out' by subtraction + false // scale isotropically to target size + }, + + { + // mapped to BarWipePolyPolygon(nBars=5): + animations::TransitionType::BLINDSWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarWipePolyPolygon(nBars=5): + animations::TransitionType::BLINDSWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::TOPLEFT, // (3) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // possible via bottomRight + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::TOPRIGHT, // (4) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // possible via bottomLeft + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::BOTTOMRIGHT, // (5) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // possible via topLeft + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::BOTTOMLEFT, // (6) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // possible via topRight + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::TOPCENTER, // (23) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::RIGHTCENTER, // (24) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::BOTTOMCENTER, // (25) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxWipe: + animations::TransitionType::BOXWIPE, + animations::TransitionSubType::LEFTCENTER, // (26) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FourBoxWipe: + animations::TransitionType::FOURBOXWIPE, + animations::TransitionSubType::CORNERSIN, // (7) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FourBoxWipe: + animations::TransitionType::FOURBOXWIPE, + animations::TransitionSubType::CORNERSOUT, // (8) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to BarnDoorWipe: + animations::TransitionType::BARNDOORWIPE, + animations::TransitionSubType::VERTICAL, // (21) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarnDoorWipe: + animations::TransitionType::BARNDOORWIPE, + animations::TransitionSubType::HORIZONTAL, // (22) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarnDoorWipe: + animations::TransitionType::BARNDOORWIPE, + animations::TransitionSubType::DIAGONALBOTTOMLEFT, // (45) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 45.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarnDoorWipe: + animations::TransitionType::BARNDOORWIPE, + animations::TransitionSubType::DIAGONALTOPLEFT, // (46) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -45.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to BarWipePolyPolygon: + animations::TransitionType::DIAGONALWIPE, + animations::TransitionSubType::TOPLEFT, // (41) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 45.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarWipePolyPolygon: + animations::TransitionType::DIAGONALWIPE, + animations::TransitionSubType::TOPRIGHT, // (42) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 135.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + + { + animations::TransitionType::BOWTIEWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::BOWTIEWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to BarnDoorWipe (doubled=true): + animations::TransitionType::MISCDIAGONALWIPE, + animations::TransitionSubType::DOUBLEBARNDOOR, // (47) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 45.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to DoubleDiamondWipe: + animations::TransitionType::MISCDIAGONALWIPE, + animations::TransitionSubType::DOUBLEDIAMOND, // (48) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to VeeWipe: + animations::TransitionType::VEEWIPE, + animations::TransitionSubType::DOWN, // (61) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to VeeWipe: + animations::TransitionType::VEEWIPE, + animations::TransitionSubType::LEFT, // (62) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::VEEWIPE, + animations::TransitionSubType::UP, // (63) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::VEEWIPE, + animations::TransitionSubType::RIGHT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + + { + animations::TransitionType::BARNVEEWIPE, + animations::TransitionSubType::TOP, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::BARNVEEWIPE, + animations::TransitionSubType::LEFT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::BARNVEEWIPE, + animations::TransitionSubType::UP, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::BARNVEEWIPE, + animations::TransitionSubType::RIGHT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to ZigZagWipe: + animations::TransitionType::ZIGZAGWIPE, + animations::TransitionSubType::LEFTTORIGHT, // (71) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ZigZagWipe: + animations::TransitionType::ZIGZAGWIPE, + animations::TransitionSubType::TOPTOBOTTOM, // (72) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarnZigZagWipe: + animations::TransitionType::BARNZIGZAGWIPE, + animations::TransitionSubType::VERTICAL, // (73) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BarnZigZagWipe: + animations::TransitionType::BARNZIGZAGWIPE, + animations::TransitionSubType::HORIZONTAL, // (74) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to IrisWipe: + animations::TransitionType::IRISWIPE, + animations::TransitionSubType::RECTANGLE, // (101) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to IrisWipe: + animations::TransitionType::IRISWIPE, + animations::TransitionSubType::DIAMOND, // (102) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 45.0, // rotation + M_SQRT2, // scaling + M_SQRT2, // scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + + { + // mapped to FigureWipe(triangle): + animations::TransitionType::TRIANGLEWIPE, + animations::TransitionSubType::UP, // (103) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(triangle): + animations::TransitionType::TRIANGLEWIPE, + animations::TransitionSubType::RIGHT, // (104) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(triangle): + animations::TransitionType::TRIANGLEWIPE, + animations::TransitionSubType::DOWN, // (105) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(triangle): + animations::TransitionType::TRIANGLEWIPE, + animations::TransitionSubType::LEFT, // (106) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FigureWipe(arrowHead): + animations::TransitionType::ARROWHEADWIPE, + animations::TransitionSubType::UP, // (107) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(arrowHead): + animations::TransitionType::ARROWHEADWIPE, + animations::TransitionSubType::RIGHT, // (108) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(arrowHead): + animations::TransitionType::ARROWHEADWIPE, + animations::TransitionSubType::DOWN, // (109) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(arrowHead): + animations::TransitionType::ARROWHEADWIPE, + animations::TransitionSubType::LEFT, // (110) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FigureWipe(pentagon): + animations::TransitionType::PENTAGONWIPE, + animations::TransitionSubType::UP, // (111) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(pentagon): + animations::TransitionType::PENTAGONWIPE, + animations::TransitionSubType::DOWN, // (112) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FigureWipe(hexagon): + animations::TransitionType::HEXAGONWIPE, + animations::TransitionSubType::HORIZONTAL, // (113) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(hexagon): + animations::TransitionType::HEXAGONWIPE, + animations::TransitionSubType::VERTICAL, // (114) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to EllipseWipe: + animations::TransitionType::ELLIPSEWIPE, + animations::TransitionSubType::CIRCLE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size + }, + { + // mapped to EllipseWipe: + animations::TransitionType::ELLIPSEWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to EllipseWipe: + animations::TransitionType::ELLIPSEWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size + }, + + + { + animations::TransitionType::EYEWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::EYEWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::ROUNDRECTWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::ROUNDRECTWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FigureWipe(star, points=4): + animations::TransitionType::STARWIPE, + animations::TransitionSubType::FOURPOINT, // (127) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(star, points=5): + animations::TransitionType::STARWIPE, + animations::TransitionSubType::FIVEPOINT, // (128) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FigureWipe(star, points=6): + animations::TransitionType::STARWIPE, + animations::TransitionSubType::SIXPOINT, // (129) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + animations::TransitionType::MISCSHAPEWIPE, + animations::TransitionSubType::HEART, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::MISCSHAPEWIPE, + animations::TransitionSubType::KEYHOLE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to ClockWipe: + animations::TransitionType::CLOCKWIPE, + animations::TransitionSubType::CLOCKWISETWELVE, // (201) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ClockWipe: + animations::TransitionType::CLOCKWIPE, + animations::TransitionSubType::CLOCKWISETHREE, // (202) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ClockWipe: + animations::TransitionType::CLOCKWIPE, + animations::TransitionSubType::CLOCKWISESIX, // (203) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ClockWipe: + animations::TransitionType::CLOCKWIPE, + animations::TransitionSubType::CLOCKWISENINE, // (204) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::ONEBLADE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::TWOBLADEVERTICAL, // (205) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::TWOBLADEHORIZONTAL, // (206) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::THREEBLADE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::FOURBLADE, // (207) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + { + // mapped to PinWheelWipe: + animations::TransitionType::PINWHEELWIPE, + animations::TransitionSubType::EIGHTBLADE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size, like ppt + }, + + { + // mapped to SweepWipe (center=true, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISETOP, // (221) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISERIGHT, // (222) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISEBOTTOM, // (223) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISELEFT, // (224) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISETOPLEFT, // (241) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=true, flipOnYAxis=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::COUNTERCLOCKWISEBOTTOMLEFT, // (242) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::CLOCKWISEBOTTOMRIGHT, // (243) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=true, flipOnYAxis=true): + animations::TransitionType::SINGLESWEEPWIPE, + animations::TransitionSubType::COUNTERCLOCKWISETOPRIGHT, // (244) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FanWipe(center=true): + animations::TransitionType::FANWIPE, + animations::TransitionSubType::CENTERTOP, // (211) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe(center=true): + animations::TransitionType::FANWIPE, + animations::TransitionSubType::CENTERRIGHT, // (212) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe: + animations::TransitionType::FANWIPE, + animations::TransitionSubType::TOP, // (231) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe: + animations::TransitionType::FANWIPE, + animations::TransitionSubType::RIGHT, // (232) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe: + animations::TransitionType::FANWIPE, + animations::TransitionSubType::BOTTOM, // (233) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe: + animations::TransitionType::FANWIPE, + animations::TransitionSubType::LEFT, // (234) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to FanWipe(center=true, single=false, fanIn=false): + animations::TransitionType::DOUBLEFANWIPE, + animations::TransitionSubType::FANOUTVERTICAL, // (213) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe(center=true, single=false, fanIn=false): + animations::TransitionType::DOUBLEFANWIPE, + animations::TransitionSubType::FANOUTHORIZONTAL, // (214) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe(center=true, single=false, fanIn=true): + animations::TransitionType::DOUBLEFANWIPE, + animations::TransitionSubType::FANINVERTICAL, // (235) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to FanWipe(center=true, single=false, fanIn=true): + animations::TransitionType::DOUBLEFANWIPE, + animations::TransitionSubType::FANINHORIZONTAL, // (236) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to SweepWipe (center=true, single=false): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::PARALLELVERTICAL, // (225) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=false): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::PARALLELDIAGONAL, // (226) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=false, + // oppositeVertical=true): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::OPPOSITEVERTICAL, // (227) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=true, single=false, + // oppositeVertical=true): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::OPPOSITEHORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=false): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::PARALLELDIAGONALTOPLEFT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SweepWipe (center=false, single=false): + animations::TransitionType::DOUBLESWEEPWIPE, + animations::TransitionSubType::PARALLELDIAGONALBOTTOMLEFT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + animations::TransitionType::SALOONDOORWIPE, + animations::TransitionSubType::TOP, // (251) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SALOONDOORWIPE, + animations::TransitionSubType::LEFT, // (252) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SALOONDOORWIPE, + animations::TransitionSubType::BOTTOM, // (253) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SALOONDOORWIPE, + animations::TransitionSubType::RIGHT, // (254) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::WINDSHIELDWIPE, + animations::TransitionSubType::RIGHT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::WINDSHIELDWIPE, + animations::TransitionSubType::UP, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::WINDSHIELDWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::WINDSHIELDWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to SnakeWipe: + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::TOPLEFTHORIZONTAL, // (301) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SnakeWipe(flipOnYAxis=true): + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::TOPLEFTVERTICAL, // (302) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SnakeWipe(diagonal=true): + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::TOPLEFTDIAGONAL, // (303) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SnakeWipe(diagonal=true, flipOnYAxis=true): + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::TOPRIGHTDIAGONAL, // (304) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SnakeWipe(diagonal=true): + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::BOTTOMRIGHTDIAGONAL, // (305) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SnakeWipe(diagonal=true, flipOnYAxis=true): + animations::TransitionType::SNAKEWIPE, + animations::TransitionSubType::BOTTOMLEFTDIAGONAL, // (306) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to SpiralWipe: + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::TOPLEFTCLOCKWISE, // (310) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe: + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::TOPRIGHTCLOCKWISE, // (311) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe: + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::BOTTOMRIGHTCLOCKWISE, // (312) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe: + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::BOTTOMLEFTCLOCKWISE, // (313) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe(flipOnYAxis=true): + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::TOPLEFTCOUNTERCLOCKWISE, // (314) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe(flipOnYAxis=true): + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::TOPRIGHTCOUNTERCLOCKWISE, // (315) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe(flipOnYAxis=true): + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::BOTTOMRIGHTCOUNTERCLOCKWISE, // (316) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 270.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to SpiralWipe(flipOnYAxis=true): + animations::TransitionType::SPIRALWIPE, + animations::TransitionSubType::BOTTOMLEFTCOUNTERCLOCKWISE, // (317) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::SubtractAndInvert, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to ParallelSnakesWipe: + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::VERTICALTOPSAME, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe: + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::VERTICALBOTTOMSAME, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (opposite=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::VERTICALTOPLEFTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (flipOnYAxis=true, opposite=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::VERTICALBOTTOMLEFTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe: + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::HORIZONTALLEFTSAME, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe: + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::HORIZONTALRIGHTSAME, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (flipOnYAxis=true, opposite=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::HORIZONTALTOPLEFTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (opposite=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::HORIZONTALTOPRIGHTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (diagonal=true, opposite=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::DIAGONALBOTTOMLEFTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to ParallelSnakesWipe (diagonal=true, opposite=true, + // flipOnYAxis=true): + animations::TransitionType::PARALLELSNAKESWIPE, + animations::TransitionSubType::DIAGONALTOPLEFTOPPOSITE, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to BoxSnakesWipe: + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::TWOBOXTOP, // (340) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxSnakesWipe: + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::TWOBOXBOTTOM, // (341) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxSnakesWipe: + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::TWOBOXLEFT, // (342) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxSnakesWipe: + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::TWOBOXRIGHT, // (343) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 180.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxSnakesWipe(fourBox=true): + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::FOURBOXVERTICAL, // (344) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to BoxSnakesWipe(fourBox=true): + animations::TransitionType::BOXSNAKESWIPE, + animations::TransitionSubType::FOURBOXHORIZONTAL, // (345) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to WaterfallWipe: + animations::TransitionType::WATERFALLWIPE, + animations::TransitionSubType::VERTICALLEFT, // (350) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to WaterfallWipe (flipOnYAxis=true): + animations::TransitionType::WATERFALLWIPE, + animations::TransitionSubType::VERTICALRIGHT, // (351) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to WaterfallWipe (flipOnYAxis=true): + animations::TransitionType::WATERFALLWIPE, + animations::TransitionSubType::HORIZONTALLEFT, // (352) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + -90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to WaterfallWipe, flipOnYAxis=false: + animations::TransitionType::WATERFALLWIPE, + animations::TransitionSubType::HORIZONTALRIGHT, // (353) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Rotate180, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMTOP, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMBOTTOM, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMBOTTOMRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMBOTTOMLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMTOPRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::FROMTOPLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::COMBHORIZONTAL, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::PUSHWIPE, + animations::TransitionSubType::COMBVERTICAL, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMTOP, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMBOTTOM, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMBOTTOMRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMTOPRIGHT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMTOPLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::SLIDEWIPE, + animations::TransitionSubType::FROMBOTTOMLEFT, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, // special code for this transition + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::FADE, + animations::TransitionSubType::CROSSFADE, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::FADE, + animations::TransitionSubType::FADETOCOLOR, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::FADE, + animations::TransitionSubType::FADEFROMCOLOR, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + animations::TransitionType::FADE, + animations::TransitionSubType::FADEOVERCOLOR, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + // this is the cut through black fade (does not fade, but does a + // hard cut) + { + animations::TransitionType::BARWIPE, + animations::TransitionSubType::FADEOVERCOLOR, + TransitionInfo::TRANSITION_SPECIAL, + // TODO(F2): Setup parameters + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to RandomWipe: + animations::TransitionType::RANDOMBARWIPE, + animations::TransitionSubType::VERTICAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to RandomWipe: + animations::TransitionType::RANDOMBARWIPE, + animations::TransitionSubType::HORIZONTAL, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to CheckerBoard: + animations::TransitionType::CHECKERBOARDWIPE, + animations::TransitionSubType::DOWN, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 90.0, // rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipY, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + { + // mapped to CheckerBoard: + animations::TransitionType::CHECKERBOARDWIPE, + animations::TransitionSubType::ACROSS, // (default) + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::FlipX, + true, // 'out' by parameter sweep inversion + false // scale isotropically to target size + }, + + { + // mapped to RandomWipe: + animations::TransitionType::DISSOLVE, + animations::TransitionSubType::DEFAULT, + TransitionInfo::TRANSITION_CLIP_POLYPOLYGON, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size + }, + + + // NOTE: This entry MUST be the last, to keep + // createSlideTransition() from infinite recursion. Because + // getRandomTransitionInfo() below will exclude the last entry of + // the table from the random number generation. + + { + // specially handled + animations::TransitionType::RANDOM, + animations::TransitionSubType::DEFAULT, + TransitionInfo::TRANSITION_SPECIAL, + 0.0, // no rotation + 1.0, // no scaling + 1.0, // no scaling + TransitionInfo::ReverseMethod::Ignore, + true, // 'out' by parameter sweep inversion + true // scale isotropically to target size + } + + // NOTE: DON'T add after this entry! See comment above! + +}; + +} // anon namespace + +const TransitionInfo* getTransitionInfo( + sal_Int16 nTransitionType, sal_Int16 nTransitionSubType ) +{ + static const TransitionInfo* pTableEnd = lcl_transitionInfo+ + SAL_N_ELEMENTS(lcl_transitionInfo); + + const TransitionInfo* pRes = ::std::find_if( + lcl_transitionInfo, pTableEnd, + TransitionInfo::Comparator( nTransitionType, + nTransitionSubType ) ); + if (pRes != pTableEnd) + return pRes; + else + return nullptr; +} + +const TransitionInfo* getRandomTransitionInfo() +{ + return lcl_transitionInfo + getRandomOrdinal( + SAL_N_ELEMENTS(lcl_transitionInfo) + - 1 /* exclude random transition at end of table */ ); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/transitionfactorytab.hxx b/slideshow/source/engine/transitions/transitionfactorytab.hxx new file mode 100644 index 000000000..ba7177f66 --- /dev/null +++ b/slideshow/source/engine/transitions/transitionfactorytab.hxx @@ -0,0 +1,38 @@ +/* -*- 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_TRANSITIONS_TRANSITIONFACTORYTAB_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_TRANSITIONFACTORYTAB_HXX + +#include <sal/config.h> + +#include <sal/types.h> + +namespace slideshow::internal +{ +struct TransitionInfo; + +TransitionInfo const* getTransitionInfo(sal_Int16 nTransitionType, sal_Int16 nTransitionSubType); + +TransitionInfo const* getRandomTransitionInfo(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/transitiontools.cxx b/slideshow/source/engine/transitions/transitiontools.cxx new file mode 100644 index 000000000..e930028f6 --- /dev/null +++ b/slideshow/source/engine/transitions/transitiontools.cxx @@ -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 . + */ + + +#include "transitiontools.hxx" +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +namespace slideshow::internal { + +// TODO(Q2): Move this to basegfx +::basegfx::B2DPolygon createUnitRect() +{ + return ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle(0.0,0.0, + 1.0,1.0 ) ); +} + +::basegfx::B2DPolyPolygon flipOnYAxis( + ::basegfx::B2DPolyPolygon const & polypoly ) +{ + ::basegfx::B2DPolyPolygon res(polypoly); + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(-1.0, 1.0, 1.0, 0.0)); + res.flip(); + return res; +} + +::basegfx::B2DPolyPolygon flipOnXAxis( + ::basegfx::B2DPolyPolygon const & polypoly ) +{ + ::basegfx::B2DPolyPolygon res(polypoly); + res.transform(basegfx::utils::createScaleTranslateB2DHomMatrix(1.0, -1.0, 0.0, 1.0)); + res.flip(); + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/transitiontools.hxx b/slideshow/source/engine/transitions/transitiontools.hxx new file mode 100644 index 000000000..806677843 --- /dev/null +++ b/slideshow/source/engine/transitions/transitiontools.hxx @@ -0,0 +1,40 @@ +/* -*- 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_TRANSITIONS_TRANSITIONTOOLS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_TRANSITIONTOOLS_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +namespace slideshow::internal +{ +/// Create a unit rect. +::basegfx::B2DPolygon createUnitRect(); + +/// Flips on X-axis: +::basegfx::B2DPolyPolygon flipOnXAxis(::basegfx::B2DPolyPolygon const& polypoly); + +/// Flips on Y-axis: +::basegfx::B2DPolyPolygon flipOnYAxis(::basegfx::B2DPolyPolygon const& polypoly); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_TRANSITIONTOOLS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/veewipe.cxx b/slideshow/source/engine/transitions/veewipe.cxx new file mode 100644 index 000000000..48fc4304b --- /dev/null +++ b/slideshow/source/engine/transitions/veewipe.cxx @@ -0,0 +1,42 @@ +/* -*- 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 <basegfx/point/b2dpoint.hxx> +#include "veewipe.hxx" + + +namespace slideshow::internal { + +::basegfx::B2DPolyPolygon VeeWipe::operator () ( double t ) +{ + ::basegfx::B2DPolygon poly; + poly.append( ::basegfx::B2DPoint( 0.0, -1.0 ) ); + const double d = ::basegfx::pruneScaleValue( 2.0 * t ); + poly.append( ::basegfx::B2DPoint( 0.0, d - 1.0 ) ); + poly.append( ::basegfx::B2DPoint( 0.5, d ) ); + poly.append( ::basegfx::B2DPoint( 1.0, d - 1.0 ) ); + poly.append( ::basegfx::B2DPoint( 1.0, -1.0 ) ); + poly.setClosed(true); + return ::basegfx::B2DPolyPolygon( poly ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/veewipe.hxx b/slideshow/source/engine/transitions/veewipe.hxx new file mode 100644 index 000000000..cb0e3b281 --- /dev/null +++ b/slideshow/source/engine/transitions/veewipe.hxx @@ -0,0 +1,40 @@ +/* -*- 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_TRANSITIONS_VEEWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_VEEWIPE_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include "parametricpolypolygon.hxx" + +namespace slideshow::internal +{ +/// Generate a vee wipe +class VeeWipe : public ParametricPolyPolygon +{ +public: + VeeWipe() {} + virtual ::basegfx::B2DPolyPolygon operator()(double x) override; +}; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_VEEWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/waterfallwipe.cxx b/slideshow/source/engine/transitions/waterfallwipe.cxx new file mode 100644 index 000000000..c3ef81ca5 --- /dev/null +++ b/slideshow/source/engine/transitions/waterfallwipe.cxx @@ -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 . + */ + + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "waterfallwipe.hxx" +#include "transitiontools.hxx" + + +namespace slideshow::internal { + +WaterfallWipe::WaterfallWipe( sal_Int32 nElements, bool flipOnYAxis ) + : m_flipOnYAxis( flipOnYAxis ) +{ + const sal_Int32 sqrtElements = static_cast<sal_Int32>( + sqrt( static_cast<double>(nElements) ) ); + const double elementEdge = 1.0 / sqrtElements; + m_waterfall.append( ::basegfx::B2DPoint( 0.0, -1.0 ) ); + for ( sal_Int32 pos = sqrtElements; pos--; ) + { + const sal_Int32 xPos = sqrtElements - pos - 1; + const double yPos = ::basegfx::pruneScaleValue( ((pos + 1) * elementEdge) - 1.0 ); + m_waterfall.append( ::basegfx::B2DPoint( + ::basegfx::pruneScaleValue( xPos * elementEdge ), + yPos ) ); + m_waterfall.append( ::basegfx::B2DPoint( + ::basegfx::pruneScaleValue( (xPos + 1) * elementEdge ), + yPos ) ); + } + m_waterfall.append( ::basegfx::B2DPoint( 1.0, -1.0 ) ); + m_waterfall.setClosed(true); +} + +::basegfx::B2DPolyPolygon WaterfallWipe::operator () ( double t ) +{ + ::basegfx::B2DPolygon poly( m_waterfall ); + poly.transform(basegfx::utils::createTranslateB2DHomMatrix(0.0, ::basegfx::pruneScaleValue(2.0 * t))); + poly.setB2DPoint( 0, ::basegfx::B2DPoint( 0.0, -1.0 ) ); + poly.setB2DPoint( poly.count()-1, ::basegfx::B2DPoint( 1.0, -1.0 ) ); + + return m_flipOnYAxis ? flipOnYAxis( ::basegfx::B2DPolyPolygon(poly) ) + : ::basegfx::B2DPolyPolygon(poly); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/waterfallwipe.hxx b/slideshow/source/engine/transitions/waterfallwipe.hxx new file mode 100644 index 000000000..821c9b7a2 --- /dev/null +++ b/slideshow/source/engine/transitions/waterfallwipe.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_WATERFALLWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_WATERFALLWIPE_HXX + +#include "parametricpolypolygon.hxx" +#include <basegfx/polygon/b2dpolygon.hxx> + + +namespace slideshow::internal { + +/// Generate a vertical left waterfall wipe +class WaterfallWipe : public ParametricPolyPolygon +{ +public: + WaterfallWipe( sal_Int32 nElements, bool flipOnYAxis ); + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +private: + bool m_flipOnYAxis; + ::basegfx::B2DPolygon m_waterfall; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_WATERFALLWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/zigzagwipe.cxx b/slideshow/source/engine/transitions/zigzagwipe.cxx new file mode 100644 index 000000000..9b1f14221 --- /dev/null +++ b/slideshow/source/engine/transitions/zigzagwipe.cxx @@ -0,0 +1,70 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "transitiontools.hxx" +#include "zigzagwipe.hxx" + + +namespace slideshow::internal { + +ZigZagWipe::ZigZagWipe( sal_Int32 nZigs ) : m_zigEdge( 1.0 / nZigs ) +{ + const double d = m_zigEdge; + const double d2 = d / 2.0; + m_stdZigZag.append( ::basegfx::B2DPoint( -1.0 - d, -d ) ); + m_stdZigZag.append( ::basegfx::B2DPoint( -1.0 - d, 1.0 + d ) ); + m_stdZigZag.append( ::basegfx::B2DPoint( -d, 1.0 + d ) ); + for ( sal_Int32 pos = nZigs + 2; pos--; ) { + m_stdZigZag.append( ::basegfx::B2DPoint( 0.0, ((pos - 1) * d) + d2 ) ); + m_stdZigZag.append( ::basegfx::B2DPoint( -d, (pos - 1) * d ) ); + } + m_stdZigZag.setClosed(true); +} + +::basegfx::B2DPolyPolygon ZigZagWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res(m_stdZigZag); + res.transform(basegfx::utils::createTranslateB2DHomMatrix((1.0 + m_zigEdge) * t, 0.0)); + return res; +} + +::basegfx::B2DPolyPolygon BarnZigZagWipe::operator () ( double t ) +{ + ::basegfx::B2DPolyPolygon res( createUnitRect() ); + ::basegfx::B2DPolygon poly( m_stdZigZag ); + poly.flip(); + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + (1.0 + m_zigEdge) * (1.0 - t) / 2.0, 0.0)); + poly.transform( aTransform ); + res.append( poly ); + aTransform.scale( -1.0, 1.0 ); + aTransform.translate( 1.0, m_zigEdge / 2.0 ); + poly = m_stdZigZag; + poly.transform( aTransform ); + res.append( poly ); + return res; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/transitions/zigzagwipe.hxx b/slideshow/source/engine/transitions/zigzagwipe.hxx new file mode 100644 index 000000000..2126483eb --- /dev/null +++ b/slideshow/source/engine/transitions/zigzagwipe.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_TRANSITIONS_ZIGZAGWIPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_ZIGZAGWIPE_HXX + +#include "parametricpolypolygon.hxx" +#include <basegfx/polygon/b2dpolygon.hxx> + + +namespace slideshow::internal { + +/// Generates a left to right zigZag wipe: +class ZigZagWipe : public ParametricPolyPolygon +{ +public: + explicit ZigZagWipe( sal_Int32 nZigs ); + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +protected: + const double m_zigEdge; + ::basegfx::B2DPolygon m_stdZigZag; +}; + +/// Generates a vertical barnZigZag wipe: +class BarnZigZagWipe : public ZigZagWipe +{ +public: + explicit BarnZigZagWipe( sal_Int32 nZigs ) : ZigZagWipe(nZigs) {} + virtual ::basegfx::B2DPolyPolygon operator () ( double t ) override; +}; + + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_TRANSITIONS_ZIGZAGWIPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/unoviewcontainer.cxx b/slideshow/source/engine/unoviewcontainer.cxx new file mode 100644 index 000000000..69537433a --- /dev/null +++ b/slideshow/source/engine/unoviewcontainer.cxx @@ -0,0 +1,99 @@ +/* -*- 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 <unoviewcontainer.hxx> + +#include <osl/diagnose.h> + +#include <algorithm> + + +using namespace ::com::sun::star; + + +namespace slideshow::internal +{ + UnoViewContainer::UnoViewContainer() : + maViews() + { + } + + bool UnoViewContainer::addView( const UnoViewSharedPtr& rView ) + { + // check whether same view is already added + + uno::Reference< presentation::XSlideShowView > rTmpView = rView->getUnoView(); + // already added? + if( ::std::any_of( maViews.begin(), + maViews.end(), + [&rTmpView]( const UnoViewSharedPtr& pView ) + { return rTmpView == pView->getUnoView(); } ) ) + { + // yes, nothing to do + return false; + } + + // add locally + maViews.push_back( rView ); + + return true; + } + + UnoViewSharedPtr UnoViewContainer::removeView( const uno::Reference< presentation::XSlideShowView >& xView ) + { + // check whether same view is already added + const UnoViewVector::iterator aEnd( maViews.end() ); + UnoViewVector::iterator aIter; + + // added in the first place? + if( (aIter=::std::find_if( maViews.begin(), + aEnd, + [&xView]( const UnoViewSharedPtr& pView ) + { return xView == pView->getUnoView(); } )) == aEnd ) + { + // nope, nothing to do + return UnoViewSharedPtr(); + } + + OSL_ENSURE( + ::std::count_if( + maViews.begin(), + aEnd, + [&xView]( const UnoViewSharedPtr& pView ) + { return xView == pView->getUnoView(); } ) == 1, + "UnoViewContainer::removeView(): View was added multiple times" ); + + UnoViewSharedPtr pView( *aIter ); + + // actually erase from container + maViews.erase( aIter ); + + return pView; + } + + void UnoViewContainer::dispose() + { + for( const auto& pView : maViews ) + pView->_dispose(); + maViews.clear(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/usereventqueue.cxx b/slideshow/source/engine/usereventqueue.cxx new file mode 100644 index 000000000..bd3e1820e --- /dev/null +++ b/slideshow/source/engine/usereventqueue.cxx @@ -0,0 +1,791 @@ +/* -*- 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/awt/SystemPointer.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> + +#include <delayevent.hxx> +#include <usereventqueue.hxx> +#include <cursormanager.hxx> + +#include <vector> +#include <queue> +#include <map> +#include <algorithm> + + +using namespace com::sun::star; + +/* Implementation of UserEventQueue class */ + +namespace slideshow::internal { + +namespace { + +typedef std::vector<EventSharedPtr> ImpEventVector; +typedef std::queue<EventSharedPtr> ImpEventQueue; +typedef std::map<uno::Reference<animations::XAnimationNode>, + ImpEventVector> ImpAnimationEventMap; +typedef std::map<ShapeSharedPtr, ImpEventQueue, + Shape::lessThanShape> ImpShapeEventMap; + +// MouseEventHandler base class, not consuming any event: +class MouseEventHandler_ : public MouseEventHandler +{ +public: + virtual bool handleMousePressed( awt::MouseEvent const& /*e*/ ) override { return false;} + virtual bool handleMouseReleased( awt::MouseEvent const& /*e*/) override { return false;} + virtual bool handleMouseDragged( awt::MouseEvent const& /*e*/ ) override { return false;} + virtual bool handleMouseMoved( awt::MouseEvent const& /*e*/ ) override { return false; } +}; + +/** @return one event has been posted + */ +template <typename ContainerT> +bool fireSingleEvent( ContainerT & rQueue, EventQueue & rEventQueue ) +{ + // post next event in given queue: + while (! rQueue.empty()) + { + EventSharedPtr const pEvent(rQueue.front()); + rQueue.pop(); + + // skip all inactive events (as the purpose of + // nextEventFromQueue() is to activate the next + // event, and events which return false on + // isCharged() will never be activated by the + // EventQueue) + if(pEvent->isCharged()) + return rEventQueue.addEvent( pEvent ); + } + return false; // no more (active) events in queue +} + +/** @return at least one event has been posted + */ +template <typename ContainerT> +bool fireAllEvents( ContainerT & rQueue, EventQueue & rEventQueue ) +{ + bool bFiredAny = false; + while (fireSingleEvent( rQueue, rEventQueue )) + bFiredAny = true; + return bFiredAny; +} + +class EventContainer +{ +public: + EventContainer() : + maEvents() + {} + + void addEvent( const EventSharedPtr& rEvent ) + { + maEvents.push( rEvent ); + } + +protected: + ImpEventQueue maEvents; +}; + +} // anon namespace + +class AllAnimationEventHandler : public AnimationEventHandler +{ +public: + explicit AllAnimationEventHandler( EventQueue& rEventQueue ) : + mrEventQueue( rEventQueue ), + maAnimationEventMap() + {} + + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override + { + ENSURE_OR_RETURN_FALSE( + rNode, + "AllAnimationEventHandler::handleAnimationEvent(): Invalid node" ); + + bool bRet( false ); + + ImpAnimationEventMap::iterator aIter; + if( (aIter=maAnimationEventMap.find( + rNode->getXAnimationNode() )) != maAnimationEventMap.end() ) + { + ImpEventVector& rVec( aIter->second ); + + bRet = !rVec.empty(); + + // registered node found -> fire all events in the vector + for( const auto& pEvent : rVec ) + mrEventQueue.addEvent( pEvent ); + + rVec.clear(); + } + + return bRet; + } + + void addEvent( const EventSharedPtr& rEvent, + const uno::Reference< animations::XAnimationNode >& xNode ) + { + ImpAnimationEventMap::iterator aIter; + if( (aIter=maAnimationEventMap.find( xNode )) == + maAnimationEventMap.end() ) + { + // no entry for this animation -> create one + aIter = maAnimationEventMap.emplace( xNode, ImpEventVector() ).first; + } + + // add new event to queue + aIter->second.push_back( rEvent ); + } + +private: + EventQueue& mrEventQueue; + ImpAnimationEventMap maAnimationEventMap; +}; + +class ClickEventHandler : public MouseEventHandler_, + public EventHandler, + public EventContainer +{ +public: + explicit ClickEventHandler( EventQueue& rEventQueue ) : + EventContainer(), + mrEventQueue( rEventQueue ), + mbAdvanceOnClick( true ) + {} + + void setAdvanceOnClick( bool bAdvanceOnClick ) + { + mbAdvanceOnClick = bAdvanceOnClick; + } + +private: + + // triggered by API calls, e.g. space bar + virtual bool handleEvent() override + { + return handleEvent_impl(); + } + + // triggered by mouse release: + virtual bool handleMouseReleased( const awt::MouseEvent& evt ) override + { + if(evt.Buttons != awt::MouseButton::LEFT) + return false; + + if( mbAdvanceOnClick ) { + // fire next event + return handleEvent_impl(); + } + else { + return false; // advance-on-click disabled + } + } + + // triggered by both: + virtual bool handleEvent_impl() + { + // fire next event: + return fireSingleEvent( maEvents, mrEventQueue ); + } + +private: + EventQueue& mrEventQueue; + bool mbAdvanceOnClick; +}; + +class SkipEffectEventHandler : public ClickEventHandler +{ +public: + SkipEffectEventHandler( EventQueue & rEventQueue, + EventMultiplexer & rEventMultiplexer ) + : ClickEventHandler(rEventQueue), + mrEventQueue(rEventQueue), + mrEventMultiplexer(rEventMultiplexer), + mbSkipTriggersNextEffect(true) {} + + /** Remember to trigger (or not to trigger) the next effect after the + current effect is skipped. + */ + void setSkipTriggersNextEffect (const bool bSkipTriggersNextEffect) + { mbSkipTriggersNextEffect = bSkipTriggersNextEffect; } + + /// Skip the current effect but do not trigger the next effect. + void skipEffect() { handleEvent_impl(false); } + +private: + virtual bool handleEvent_impl() override + { + return handleEvent_impl(true); + } + + bool handleEvent_impl (bool bNotifyNextEffect) + { + // fire all events, so animation nodes can register their + // next effect listeners: + if(fireAllEvents( maEvents, mrEventQueue )) + { + if (mbSkipTriggersNextEffect && bNotifyNextEffect) + { + // then simulate a next effect event: this skip effect + // handler is triggered upon next effect events (multiplexer + // prio=-1)! Posting a notifyNextEffect() here is only safe + // (we don't run into busy loop), because we assume that + // someone has registered above for next effects + // (multiplexer prio=0) at the user event queue. + return mrEventQueue.addEventWhenQueueIsEmpty( + makeEvent( [this] () { + this->mrEventMultiplexer.notifyNextEffect(); + }, "EventMultiplexer::notifyNextEffect") ); + } + else + return true; + } + return false; + } + +private: + EventQueue & mrEventQueue; + EventMultiplexer & mrEventMultiplexer; + bool mbSkipTriggersNextEffect; +}; + +namespace { + +/** Base class to share some common code between + ShapeClickEventHandler and MouseMoveHandler + + @derive override necessary MouseEventHandler interface methods, + call sendEvent() method to actually process the event. +*/ +class MouseHandlerBase : public MouseEventHandler_ +{ +public: + explicit MouseHandlerBase( EventQueue& rEventQueue ) : + mrEventQueue( rEventQueue ), + maShapeEventMap() + {} + + void addEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) + { + ImpShapeEventMap::iterator aIter; + if( (aIter=maShapeEventMap.find( rShape )) == maShapeEventMap.end() ) + { + // no entry for this shape -> create one + aIter = maShapeEventMap.emplace(rShape, ImpEventQueue()).first; + } + + // add new event to queue + aIter->second.push( rEvent ); + } + +protected: + bool hitTest( const awt::MouseEvent& e, + ImpShapeEventMap::reverse_iterator& o_rHitShape ) + { + // find hit shape in map + const basegfx::B2DPoint aPosition( e.X, e.Y ); + + // find matching shape (scan reversely, to coarsely match + // paint order) + auto aCurrShape = std::find_if(maShapeEventMap.rbegin(), maShapeEventMap.rend(), + [&aPosition](const ImpShapeEventMap::value_type& rShape) { + // TODO(F2): Get proper geometry polygon from the + // shape, to avoid having areas outside the shape + // react on the mouse + return rShape.first->getBounds().isInside( aPosition ) + && rShape.first->isVisible(); + }); + if (aCurrShape != maShapeEventMap.rend()) + { + // shape hit, and shape is visible - report a + // hit + o_rHitShape = aCurrShape; + return true; + } + + return false; // nothing hit + } + + bool sendEvent( ImpShapeEventMap::reverse_iterator const & io_rHitShape ) + { + // take next event from queue + const bool bRet( fireSingleEvent( io_rHitShape->second, + mrEventQueue ) ); + + // clear shape entry, if its queue is + // empty. This is important, since the shapes + // are held by shared ptr, and might otherwise + // not get released, even after their owning + // slide is long gone. + if( io_rHitShape->second.empty() ) + { + // this looks funny, since ::std::map does + // provide an erase( iterator ) + // method. Unfortunately, C++ does not + // declare the obvious erase( + // reverse_iterator ) needed here (missing + // orthogonality, eh?) + maShapeEventMap.erase( io_rHitShape->first ); + } + + return bRet; + } + + bool processEvent( const awt::MouseEvent& e ) + { + ImpShapeEventMap::reverse_iterator aCurrShape; + + if( hitTest( e, aCurrShape ) ) + return sendEvent( aCurrShape ); + + return false; // did not handle the event + } + +private: + EventQueue& mrEventQueue; + ImpShapeEventMap maShapeEventMap; +}; + +} + +class ShapeClickEventHandler : public MouseHandlerBase +{ +public: + ShapeClickEventHandler( CursorManager& rCursorManager, + EventQueue& rEventQueue ) : + MouseHandlerBase( rEventQueue ), + mrCursorManager( rCursorManager ) + {} + + virtual bool handleMouseReleased( const awt::MouseEvent& e ) override + { + if(e.Buttons != awt::MouseButton::LEFT) + return false; + return processEvent( e ); + } + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // TODO(P2): Maybe buffer last shape touched + + // if we have a shape click event, and the mouse + // hovers over this shape, change cursor to hand + ImpShapeEventMap::reverse_iterator aDummy; + if( hitTest( e, aDummy ) ) + mrCursorManager.requestCursor( awt::SystemPointer::REFHAND ); + + return false; // we don't /eat/ this event. Lower prio + // handler should see it, too. + } + +private: + CursorManager& mrCursorManager; +}; + +class MouseEnterHandler : public MouseHandlerBase +{ +public: + explicit MouseEnterHandler( EventQueue& rEventQueue ) + : MouseHandlerBase( rEventQueue ), + mpLastShape() {} + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // TODO(P2): Maybe buffer last shape touched, and + // check against that _first_ + + ImpShapeEventMap::reverse_iterator aCurr; + if( hitTest( e, aCurr ) ) + { + if( aCurr->first != mpLastShape ) + { + // we actually hit a shape, and it's different + // from the previous one - thus we just + // entered it, raise event + sendEvent( aCurr ); + mpLastShape = aCurr->first; + } + } + else + { + // don't hit no shape - thus, last shape is NULL + mpLastShape.reset(); + } + + return false; // we don't /eat/ this event. Lower prio + // handler should see it, too. + } + +private: + ShapeSharedPtr mpLastShape; +}; + +class MouseLeaveHandler : public MouseHandlerBase +{ +public: + explicit MouseLeaveHandler( EventQueue& rEventQueue ) + : MouseHandlerBase( rEventQueue ), + maLastIter() {} + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // TODO(P2): Maybe buffer last shape touched, and + // check against that _first_ + + ImpShapeEventMap::reverse_iterator aCurr; + if( hitTest( e, aCurr ) ) + { + maLastIter = aCurr; + } + else + { + if( maLastIter->first ) + { + // last time, we were over a shape, now we're + // not - we thus just left that shape, raise + // event + sendEvent( maLastIter ); + } + + // in any case, when we hit this else-branch: no + // shape hit, thus have to clear maLastIter + maLastIter = ImpShapeEventMap::reverse_iterator(); + } + + return false; // we don't /eat/ this event. Lower prio + // handler should see it, too. + } + +private: + ImpShapeEventMap::reverse_iterator maLastIter; +}; + +template< typename Handler, typename Functor > +void UserEventQueue::registerEvent( + std::shared_ptr< Handler >& rHandler, + const EventSharedPtr& rEvent, + const Functor& rRegistrationFunctor ) +{ + ENSURE_OR_THROW( rEvent, + "UserEventQueue::registerEvent(): Invalid event" ); + + if( !rHandler ) { + // create handler + rHandler = std::make_shared<Handler>( mrEventQueue ); + // register handler on EventMultiplexer + rRegistrationFunctor( rHandler ); + } + + rHandler->addEvent( rEvent ); +} + +template< typename Handler, typename Arg, typename Functor > +void UserEventQueue::registerEvent( + std::shared_ptr< Handler >& rHandler, + const EventSharedPtr& rEvent, + const Arg& rArg, + const Functor& rRegistrationFunctor ) +{ + ENSURE_OR_THROW( rEvent, + "UserEventQueue::registerEvent(): Invalid event" ); + + if( !rHandler ) { + // create handler + rHandler = std::make_shared<Handler>( mrEventQueue ); + + // register handler on EventMultiplexer + rRegistrationFunctor( rHandler ); + } + + rHandler->addEvent( rEvent, rArg ); +} + + +UserEventQueue::UserEventQueue( EventMultiplexer& rMultiplexer, + EventQueue& rEventQueue, + CursorManager& rCursorManager ) + : mrMultiplexer( rMultiplexer ), + mrEventQueue( rEventQueue ), + mrCursorManager( rCursorManager ), + mpAnimationStartEventHandler(), + mpAnimationEndEventHandler(), + mpAudioStoppedEventHandler(), + mpClickEventHandler(), + mpSkipEffectEventHandler(), + mpMouseEnterHandler(), + mpMouseLeaveHandler(), + mbAdvanceOnClick( true ) +{ +} + +UserEventQueue::~UserEventQueue() +{ + try + { + // unregister all handlers + clear(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } +} + +void UserEventQueue::clear() +{ + // unregister and delete all handlers + if( mpAnimationStartEventHandler ) { + mrMultiplexer.removeAnimationStartHandler( + mpAnimationStartEventHandler ); + mpAnimationStartEventHandler.reset(); + } + if( mpAnimationEndEventHandler ) { + mrMultiplexer.removeAnimationEndHandler( mpAnimationEndEventHandler ); + mpAnimationEndEventHandler.reset(); + } + if( mpAudioStoppedEventHandler ) { + mrMultiplexer.removeAudioStoppedHandler( mpAudioStoppedEventHandler ); + mpAudioStoppedEventHandler.reset(); + } + if( mpShapeClickEventHandler ) { + mrMultiplexer.removeClickHandler( mpShapeClickEventHandler ); + mrMultiplexer.removeMouseMoveHandler( mpShapeClickEventHandler ); + mpShapeClickEventHandler.reset(); + } + if( mpClickEventHandler ) { + mrMultiplexer.removeClickHandler( mpClickEventHandler ); + mrMultiplexer.removeNextEffectHandler( mpClickEventHandler ); + mpClickEventHandler.reset(); + } + if(mpSkipEffectEventHandler) { + mrMultiplexer.removeClickHandler( mpSkipEffectEventHandler ); + mrMultiplexer.removeNextEffectHandler( mpSkipEffectEventHandler ); + mpSkipEffectEventHandler.reset(); + } + if( mpShapeDoubleClickEventHandler ) { + mrMultiplexer.removeDoubleClickHandler( mpShapeDoubleClickEventHandler ); + mrMultiplexer.removeMouseMoveHandler( mpShapeDoubleClickEventHandler ); + mpShapeDoubleClickEventHandler.reset(); + } + if( mpMouseEnterHandler ) { + mrMultiplexer.removeMouseMoveHandler( mpMouseEnterHandler ); + mpMouseEnterHandler.reset(); + } + if( mpMouseLeaveHandler ) { + mrMultiplexer.removeMouseMoveHandler( mpMouseLeaveHandler ); + mpMouseLeaveHandler.reset(); + } +} + +void UserEventQueue::setAdvanceOnClick( bool bAdvanceOnClick ) +{ + mbAdvanceOnClick = bAdvanceOnClick; + + // forward to handler, if existing. Otherwise, the handler + // creation will do the forwarding. + if( mpClickEventHandler ) + mpClickEventHandler->setAdvanceOnClick( bAdvanceOnClick ); +} + +void UserEventQueue::registerAnimationStartEvent( + const EventSharedPtr& rEvent, + const uno::Reference< animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAnimationStartEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAnimationStartHandler( rHandler ); } ); +} + +void UserEventQueue::registerAnimationEndEvent( + const EventSharedPtr& rEvent, + const uno::Reference<animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAnimationEndEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAnimationEndHandler( rHandler ); } ); +} + +void UserEventQueue::registerAudioStoppedEvent( + const EventSharedPtr& rEvent, + const uno::Reference<animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAudioStoppedEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAudioStoppedHandler( rHandler ); } ); +} + +void UserEventQueue::registerShapeClickEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + ENSURE_OR_THROW( + rEvent, + "UserEventQueue::registerShapeClickEvent(): Invalid event" ); + + if( !mpShapeClickEventHandler ) + { + // create handler + mpShapeClickEventHandler = + std::make_shared<ShapeClickEventHandler>(mrCursorManager, + mrEventQueue); + + // register handler on EventMultiplexer + mrMultiplexer.addClickHandler( mpShapeClickEventHandler, 1.0 ); + mrMultiplexer.addMouseMoveHandler( mpShapeClickEventHandler, 1.0 ); + } + + mpShapeClickEventHandler->addEvent( rEvent, rShape ); +} + +namespace { +class ClickEventRegistrationFunctor +{ +public: + ClickEventRegistrationFunctor( EventMultiplexer& rMultiplexer, + double nPrio, + bool bAdvanceOnClick ) + : mrMultiplexer( rMultiplexer ), + mnPrio(nPrio), + mbAdvanceOnClick( bAdvanceOnClick ) {} + + void operator()( const std::shared_ptr<ClickEventHandler>& rHandler )const + { + // register the handler on _two_ sources: we want the + // nextEffect events, e.g. space bar, to trigger clicks, as well! + mrMultiplexer.addClickHandler( rHandler, mnPrio ); + mrMultiplexer.addNextEffectHandler( rHandler, mnPrio ); + + // forward advance-on-click state to newly + // generated handler (that's the only reason why + // we're called here) + rHandler->setAdvanceOnClick( mbAdvanceOnClick ); + } + +private: + EventMultiplexer& mrMultiplexer; + double const mnPrio; + bool const mbAdvanceOnClick; +}; +} // anon namespace + +void UserEventQueue::registerNextEffectEvent( const EventSharedPtr& rEvent ) +{ + // TODO: better name may be mpNextEffectEventHandler? then we have + // next effect (=> waiting to be started) + // skip effect (skipping the currently running one) + // rewind effect (rewinding back running one and waiting (again) + // to be started) + registerEvent( mpClickEventHandler, + rEvent, + ClickEventRegistrationFunctor( mrMultiplexer, + 0.0 /* default prio */, + mbAdvanceOnClick ) ); +} + +void UserEventQueue::registerSkipEffectEvent( + EventSharedPtr const & pEvent, + const bool bSkipTriggersNextEffect) +{ + if(!mpSkipEffectEventHandler) + { + mpSkipEffectEventHandler = + std::make_shared<SkipEffectEventHandler>( mrEventQueue, mrMultiplexer ); + // register the handler on _two_ sources: we want the + // nextEffect events, e.g. space bar, to trigger clicks, as well! + mrMultiplexer.addClickHandler( mpSkipEffectEventHandler, + -1.0 /* prio below default */ ); + mrMultiplexer.addNextEffectHandler( mpSkipEffectEventHandler, + -1.0 /* prio below default */ ); + // forward advance-on-click state to newly + // generated handler (that's the only reason why + // we're called here) + mpSkipEffectEventHandler->setAdvanceOnClick( mbAdvanceOnClick ); + } + mpSkipEffectEventHandler->setSkipTriggersNextEffect(bSkipTriggersNextEffect); + mpSkipEffectEventHandler->addEvent( pEvent ); +} + +void UserEventQueue::registerShapeDoubleClickEvent( + const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + ENSURE_OR_THROW( + rEvent, + "UserEventQueue::registerShapeDoubleClickEvent(): Invalid event" ); + + if( !mpShapeDoubleClickEventHandler ) + { + // create handler + mpShapeDoubleClickEventHandler = + std::make_shared<ShapeClickEventHandler>(mrCursorManager, + mrEventQueue); + + // register handler on EventMultiplexer + mrMultiplexer.addDoubleClickHandler( mpShapeDoubleClickEventHandler, + 1.0 ); + mrMultiplexer.addMouseMoveHandler( mpShapeDoubleClickEventHandler, + 1.0 ); + } + + mpShapeDoubleClickEventHandler->addEvent( rEvent, rShape ); +} + +void UserEventQueue::registerMouseEnterEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + registerEvent( mpMouseEnterHandler, + rEvent, + rShape, + [this]( const MouseEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addMouseMoveHandler( rHandler, 0.0 ); } ); +} + +void UserEventQueue::registerMouseLeaveEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + registerEvent( mpMouseLeaveHandler, + rEvent, + rShape, + [this]( const MouseEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addMouseMoveHandler( rHandler, 0.0 ); } ); +} + +void UserEventQueue::callSkipEffectEventHandler() +{ + ::std::shared_ptr<SkipEffectEventHandler> pHandler ( + ::std::dynamic_pointer_cast<SkipEffectEventHandler>(mpSkipEffectEventHandler)); + if (pHandler) + pHandler->skipEffect(); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/waitsymbol.cxx b/slideshow/source/engine/waitsymbol.cxx new file mode 100644 index 000000000..83f1858e9 --- /dev/null +++ b/slideshow/source/engine/waitsymbol.cxx @@ -0,0 +1,181 @@ +/* -*- 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 <canvas/canvastools.hxx> + +#include <cppcanvas/customsprite.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <tools/diagnose_ex.h> + +#include "waitsymbol.hxx" +#include <eventmultiplexer.hxx> + +#include <algorithm> + + +using namespace com::sun::star; + +namespace slideshow::internal { + +const sal_Int32 LEFT_BORDER_SPACE = 10; +const sal_Int32 LOWER_BORDER_SPACE = 10; + +WaitSymbolSharedPtr WaitSymbol::create( const uno::Reference<rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const UnoViewContainer& rViewContainer ) +{ + WaitSymbolSharedPtr pRet( + new WaitSymbol( xBitmap, + rScreenUpdater, + rViewContainer )); + + rEventMultiplexer.addViewHandler( pRet ); + + return pRet; +} + +WaitSymbol::WaitSymbol( uno::Reference<rendering::XBitmap> const & xBitmap, + ScreenUpdater& rScreenUpdater, + const UnoViewContainer& rViewContainer ) : + mxBitmap(xBitmap), + maViews(), + mrScreenUpdater( rScreenUpdater ), + mbVisible(false) +{ + for( const auto& pView : rViewContainer ) + viewAdded( pView ); +} + +void WaitSymbol::setVisible( const bool bVisible ) +{ + if( mbVisible == bVisible ) + return; + + mbVisible = bVisible; + + for( const auto& rView : maViews ) + { + if( rView.second ) + { + if( bVisible ) + rView.second->show(); + else + rView.second->hide(); + } + } + + // sprites changed, need a screen update for this frame. + mrScreenUpdater.requestImmediateUpdate(); +} + +basegfx::B2DPoint WaitSymbol::calcSpritePos( + UnoViewSharedPtr const & rView ) const +{ + const awt::Rectangle aViewArea( rView->getUnoView()->getCanvasArea() ); + return basegfx::B2DPoint( + aViewArea.X + std::min<sal_Int32>( aViewArea.Width, LEFT_BORDER_SPACE ), + aViewArea.X + std::max<sal_Int32>( 0, + aViewArea.Height + - mxBitmap->getSize().Height + - LOWER_BORDER_SPACE ) ); +} + +void WaitSymbol::viewAdded( const UnoViewSharedPtr& rView ) +{ + cppcanvas::CustomSpriteSharedPtr sprite; + + try + { + const geometry::IntegerSize2D spriteSize( mxBitmap->getSize() ); + sprite = rView->createSprite( basegfx::B2DVector( spriteSize.Width, + spriteSize.Height ), + 1000.0 ); // sprite should be in front of all + // other sprites + rendering::ViewState viewState; + canvas::tools::initViewState( viewState ); + rendering::RenderState renderState; + canvas::tools::initRenderState( renderState ); + sprite->getContentCanvas()->getUNOCanvas()->drawBitmap( + mxBitmap, viewState, renderState ); + + sprite->setAlpha( 0.9 ); + sprite->movePixel( calcSpritePos( rView ) ); + if( mbVisible ) + sprite->show(); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + + maViews.emplace_back( rView, sprite ); +} + +void WaitSymbol::viewRemoved( const UnoViewSharedPtr& rView ) +{ + maViews.erase( + std::remove_if( + maViews.begin(), maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ), + maViews.end() ); +} + +void WaitSymbol::viewChanged( const UnoViewSharedPtr& rView ) +{ + // find entry corresponding to modified view + ViewsVecT::iterator aModifiedEntry( + std::find_if( + maViews.begin(), + maViews.end(), + [&rView] + ( const ViewsVecT::value_type& cp ) + { return rView == cp.first; } ) ); + + OSL_ASSERT( aModifiedEntry != maViews.end() ); + if( aModifiedEntry == maViews.end() ) + return; + + if( aModifiedEntry->second ) + aModifiedEntry->second->movePixel( + calcSpritePos(aModifiedEntry->first) ); +} + +void WaitSymbol::viewsChanged() +{ + // reposition sprites on all views + for( const auto& rView : maViews ) + { + if( rView.second ) + rView.second->movePixel( + calcSpritePos( rView.first ) ); + } +} + +} // namespace slideshow::internal + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/waitsymbol.hxx b/slideshow/source/engine/waitsymbol.hxx new file mode 100644 index 000000000..1044d6076 --- /dev/null +++ b/slideshow/source/engine/waitsymbol.hxx @@ -0,0 +1,88 @@ +/* -*- 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_WAITSYMBOL_HXX +#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_WAITSYMBOL_HXX + +#include <com/sun/star/rendering/XBitmap.hpp> +#include <cppcanvas/sprite.hxx> + +#include <vieweventhandler.hxx> +#include <screenupdater.hxx> +#include <eventmultiplexer.hxx> +#include <unoview.hxx> + +#include <memory> +#include <vector> + +namespace slideshow::internal { + +class EventMultiplexer; +typedef std::shared_ptr<class WaitSymbol> WaitSymbolSharedPtr; + +/// On-screen 'hour glass' for when slideshow is unresponsive +class WaitSymbol : public ViewEventHandler +{ +public: + WaitSymbol(const WaitSymbol&) = delete; + WaitSymbol& operator=(const WaitSymbol&) = delete; + + static WaitSymbolSharedPtr create( const css::uno::Reference<css::rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const UnoViewContainer& rViewContainer ); + + /** Shows the wait symbol. + */ + void show() { setVisible(true); } + + /** Hides the wait symbol. + */ + void hide() { setVisible(false); } + +private: + WaitSymbol( const css::uno::Reference<css::rendering::XBitmap>& xBitmap, + ScreenUpdater& rScreenUpdater, + const UnoViewContainer& rViewContainer ); + + // ViewEventHandler + virtual void viewAdded( const UnoViewSharedPtr& rView ) override; + virtual void viewRemoved( const UnoViewSharedPtr& rView ) override; + virtual void viewChanged( const UnoViewSharedPtr& rView ) override; + virtual void viewsChanged() override; + + void setVisible( const bool bVisible ); + ::basegfx::B2DPoint calcSpritePos( UnoViewSharedPtr const & rView ) const; + + typedef ::std::vector< + ::std::pair<UnoViewSharedPtr, + cppcanvas::CustomSpriteSharedPtr> > ViewsVecT; + + css::uno::Reference<css::rendering::XBitmap> mxBitmap; + + ViewsVecT maViews; + ScreenUpdater& mrScreenUpdater; + bool mbVisible; +}; + +} // namespace presentation::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/wakeupevent.cxx b/slideshow/source/engine/wakeupevent.cxx new file mode 100644 index 000000000..9658661dc --- /dev/null +++ b/slideshow/source/engine/wakeupevent.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 <sal/config.h> + +#include <algorithm> + +#include <wakeupevent.hxx> + + +namespace slideshow::internal +{ + WakeupEvent::WakeupEvent( + std::shared_ptr<canvas::tools::ElapsedTime> const & pTimeBase, + ActivitiesQueue& rActivityQueue ) : + Event("WakeupEvent"), + maTimer(pTimeBase), + mnNextTime(0.0), + mpActivity(), + mrActivityQueue( rActivityQueue ) + { + } + + void WakeupEvent::dispose() + { + mpActivity.reset(); + } + + bool WakeupEvent::fire() + { + if( !mpActivity ) + return false; + + return mrActivityQueue.addActivity( mpActivity ); + } + + bool WakeupEvent::isCharged() const + { + // this event won't expire, we fire every time we're + // re-inserted into the event queue. + return true; + } + + double WakeupEvent::getActivationTime( double nCurrentTime ) const + { + const double nElapsedTime( maTimer.getElapsedTime() ); + + return ::std::max( nCurrentTime, + nCurrentTime - nElapsedTime + mnNextTime ); + } + + void WakeupEvent::start() + { + // start timer + maTimer.reset(); + } + + void WakeupEvent::setNextTimeout( double rNextTime ) + { + mnNextTime = rNextTime; + } + + void WakeupEvent::setActivity( const ActivitySharedPtr& rActivity ) + { + mpActivity = rActivity; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |