diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
251 files changed, 57412 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: */ diff --git a/slideshow/source/inc/activitiesfactory.hxx b/slideshow/source/inc/activitiesfactory.hxx new file mode 100644 index 000000000..69852489c --- /dev/null +++ b/slideshow/source/inc/activitiesfactory.hxx @@ -0,0 +1,308 @@ +/* -*- 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_INC_ACTIVITIESFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITIESFACTORY_HXX + +#include <com/sun/star/animations/XAnimate.hpp> +#include <com/sun/star/animations/XAnimateColor.hpp> + +#include "animationactivity.hxx" +#include "activitiesqueue.hxx" +#include "event.hxx" +#include "eventqueue.hxx" +#include "shape.hxx" +#include "numberanimation.hxx" +#include "enumanimation.hxx" +#include "coloranimation.hxx" +#include "hslcoloranimation.hxx" +#include "stringanimation.hxx" +#include "boolanimation.hxx" +#include "pairanimation.hxx" + +#include <optional> + +/* Definition of ActivitiesFactory class */ + +namespace slideshow::internal::ActivitiesFactory +{ + /// Collection of common factory parameters + struct CommonParameters + { + CommonParameters( + const EventSharedPtr& rEndEvent, + EventQueue& rEventQueue, + ActivitiesQueue& rActivitiesQueue, + double nMinDuration, + sal_uInt32 nMinNumberOfFrames, + bool bAutoReverse, + ::std::optional<double> const& aRepeats, + double nAcceleration, + double nDeceleration, + const ShapeSharedPtr& rShape, + const ::basegfx::B2DVector& rSlideBounds ) + : mpEndEvent( rEndEvent ), + mrEventQueue( rEventQueue ), + mrActivitiesQueue( rActivitiesQueue ), + mnMinDuration( nMinDuration ), + mnMinNumberOfFrames( nMinNumberOfFrames ), + maRepeats( aRepeats ), + mnAcceleration( nAcceleration ), + mnDeceleration( nDeceleration ), + mpShape( rShape ), + maSlideBounds( rSlideBounds ), + mbAutoReverse( bAutoReverse ) {} + + /// End event to fire when animation is over + EventSharedPtr mpEndEvent; + + /// Event queue to insert the end event into. + EventQueue& mrEventQueue; + /// Event queue to insert the end event into. + ActivitiesQueue& mrActivitiesQueue; + + /** Simple duration of the activity + + Specifies the minimal simple duration of the + activity (minimal, because mnMinNumberOfFrames + might prolong the activity). According to SMIL, + this might also be indefinite, which for our + framework does not make much sense, though + (wouldn't have a clue, then, how to scale the + animation over time). + */ + double mnMinDuration; + + /** Minimal number of frames for this activity. + + This specifies the minimal number of frames this + activity will display per simple duration. If less + than this number are displayed until mnMinDuration + is over, the activity will be prolonged until + mnMinNumberOfFrames are rendered. + */ + sal_uInt32 mnMinNumberOfFrames; + + /** Number of repeats for the simple duration + + This specified the number of repeats. The + mnMinDuration times maRepeats yields the total + duration of this activity. If this value is + unspecified, the activity will repeat + indefinitely. + */ + ::std::optional<double> const maRepeats; + + /// Fraction of simple time to accelerate animation + double mnAcceleration; + + /// Fraction of simple time to decelerate animation + double mnDeceleration; + + /// Shape, to get bounds from + ShapeSharedPtr mpShape; + + /// LayerManager, to get page size from + ::basegfx::B2DVector maSlideBounds; + + /// When true, activity is played reversed after mnDuration. + bool mbAutoReverse; + }; + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a double value. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const NumberAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a double value. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const EnumAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a color value. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const ColorAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a color value. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const HSLColorAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimateColor >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a pair of double values. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const PairAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a string. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const StringAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create an activity from an XAnimate node. + + This method creates an animated activity from the + given XAnimate node, extracting all necessary + animation parameters from that. Note that due to the + animator parameter, the animation values must be + convertible to a bool value. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param xNode + The SMIL animation node to animate + */ + AnimationActivitySharedPtr createAnimateActivity( + const CommonParameters& rParms, + const BoolAnimationSharedPtr& rAnimator, + const css::uno::Reference< css::animations::XAnimate >& xNode ); + + /** Create a simple activity for the given animator + + This method is suited to create activities for custom + animations, which need a simple double value and lasts + a given timespan. This activity always generates values + from the [0,1] range. + + @param rParms + Factory parameter structure + + @param rAnimator + Animator sub-object + + @param bDirectionForward + If true, the activity goes 'forward', i.e. from 0 to + 1. With false, the direction is reversed. + */ + AnimationActivitySharedPtr createSimpleActivity( + const CommonParameters& rParms, + const NumberAnimationSharedPtr& rAnimator, + bool bDirectionForward ); + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITIESFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/activitiesqueue.hxx b/slideshow/source/inc/activitiesqueue.hxx new file mode 100644 index 000000000..9fd3df83a --- /dev/null +++ b/slideshow/source/inc/activitiesqueue.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITIESQUEUE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITIESQUEUE_HXX + +#include <deque> + +#include "activity.hxx" + +#include <canvas/elapsedtime.hxx> + +#include <memory> + + +/* Definition of ActivitiesQueue class */ + +namespace slideshow::internal + { + /** This class handles the XSprite updates needed for + animations, such as moves, scales etc. You can add + activity objects to this class, which are called in a + round-robin fashion. + */ + class ActivitiesQueue + { + public: + /** Create an ActivitiesQueue. + + @param pPresTimer + Pointer to global presentation timer. Used for + adjusting and holding global presentation time. + */ + explicit ActivitiesQueue( + const std::shared_ptr< ::canvas::tools::ElapsedTime >& pPresTimer ); + ~ActivitiesQueue(); + ActivitiesQueue(const ActivitiesQueue&) = delete; + ActivitiesQueue& operator=(const ActivitiesQueue&) = delete; + + /** Add the given activity to the queue. + */ + bool addActivity( const ActivitySharedPtr& pActivity ); + + /** Add the given activity prioritized last in the queue. + */ + bool addTailActivity( const ActivitySharedPtr& pActivity ); + + /** Process the activities queue. + + This method performs the smallest atomic processing + possible on the queue (typically, this means one + activity get processed). + */ + void process(); + + /** Call all dequeued activities' dequeued() method + */ + void processDequeued(); + + /** Query state of the queue + + @return false, if queue is empty, true otherwise + */ + bool isEmpty() const; + + /** Remove all pending activities from the queue. + */ + void clear(); + + /** Gets the queue's timer object. + */ + std::shared_ptr< ::canvas::tools::ElapsedTime > const & + getTimer() const { return mpTimer; } + + private: + std::shared_ptr< ::canvas::tools::ElapsedTime > mpTimer; + + typedef ::std::deque< ActivitySharedPtr > ActivityQueue; + + ActivityQueue maCurrentActivitiesWaiting; // currently running + // activities, that still + // await processing for this + // round + + ActivityQueue maCurrentTailActivitiesWaiting; // activities that will be + // processed last in the queue + + ActivityQueue maCurrentActivitiesReinsert; // currently running + // activities, that are + // already processed for + // this round, and wants + // to be reinserted next + // round + + ActivityQueue maDequeuedActivities; // This list collects all activities which did not request + // a reinsertion. After the screen update has been + // performed, those are notified via dequeued(). This + // facilitates cleanup actions taking place _after_ the + // current frame has been displayed. + }; + +} +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITIESQUEUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/activity.hxx b/slideshow/source/inc/activity.hxx new file mode 100644 index 000000000..5de30c79d --- /dev/null +++ b/slideshow/source/inc/activity.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_INC_ACTIVITY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITY_HXX + +#include <memory> + +#include "disposable.hxx" + + +/* Definition of Activity interface */ + +namespace slideshow::internal + { + + class Activity : public Disposable, public virtual SharedPtrAble + { + public: + /** Perform the activity associated with this interface's + implementation. + + @return true, if activity continues, or false, if activity has + ended. + */ + virtual bool perform() = 0; + + /** Calculates whether the activity lags time. + + If this method returns a time lag greater than 0.0, + the ActivitiesQueue will adjust the global slideshow + time, by subtracting the given amount of lag. + + @return time lag or 0.0. Value must be greater or + equal than zero. + */ + virtual double calcTimeLag() const = 0; + + /** Query whether this activity is still continuing + + @return true, if this activity still + continues. Returns false, if activity has ended. It is + required that operator() returns false, when + isActive() returns false. Furthermore, it is required + that the inactive state is persistent; an activity + that has become inactive (i.e. isActive() once + returned false) must stay in that state eternally. + */ + virtual bool isActive() const = 0; + + /** Notifies the Activity that it has now left the + ActivitiesQueue + + Use this method to react on the queue removal + event. For animated shapes, this is e.g. used to + switch back to the non-sprite presentation mode of the + shape. + */ + virtual void dequeued() = 0; + + /** Forces this activity deactivate and get to its end state + (if possible), but does _not_ dispose. + */ + virtual void end() = 0; + }; + + typedef ::std::shared_ptr< Activity > ActivitySharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ACTIVITY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animatableshape.hxx b/slideshow/source/inc/animatableshape.hxx new file mode 100644 index 000000000..7cefad0f6 --- /dev/null +++ b/slideshow/source/inc/animatableshape.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_INC_ANIMATABLESHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATABLESHAPE_HXX + +#include <memory> + +#include "shape.hxx" + + +namespace slideshow::internal + { + /** Represents an animatable shape. + + This interface adds animation handling methods to a + shape. It allows transparent switching between + sprite-based viewing and static painting, depending on + whether animations are currently running. + */ + class AnimatableShape : public Shape + { + public: + // Animation methods + + + /** Notify the Shape that an animation starts now + + This method enters animation mode on all registered + views. + + @attention This method is supposed to be called only + from the LayerManager, since it might involve shifting + shapes between different layers (and removing this + shape from the background layer in the first place) + */ + virtual void enterAnimationMode() = 0; + + /** Notify the Shape that it is no longer animated + + This methods requests the Shape to end animation mode + on all registered views, if called more or equal the + times enterAnimationMode() was called. That is, the + Shape only leaves animation mode, if all requested + enterAnimationMode() call sites have issued their + matching leaveAnimationMode(). + + @attention This method is supposed to be called only + from the LayerManager, since it might involve shifting + shapes between different layers (and adding this + shape to the background layer again) + */ + virtual void leaveAnimationMode() = 0; + + }; + + typedef ::std::shared_ptr< AnimatableShape > AnimatableShapeSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATABLESHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animatedsprite.hxx b/slideshow/source/inc/animatedsprite.hxx new file mode 100644 index 000000000..18d4eecb2 --- /dev/null +++ b/slideshow/source/inc/animatedsprite.hxx @@ -0,0 +1,158 @@ +/* -*- 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_INC_ANIMATEDSPRITE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATEDSPRITE_HXX + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include "viewlayer.hxx" + +#include <optional> +#include <memory> + + +/* Definition of AnimatedSprite class */ + +namespace slideshow::internal + { + /** This class provides the sprite for animated shapes. + + Besides encapsulating the Canvas sprite for animated + shapes, this class also handles dynamic sprite resizing + and all the gory details of offset calculations and + rounding prevention. + */ + class AnimatedSprite + { + public: + /** Create a new AnimatedSprite, for the given metafile + shape. + + @param rViewLayer + The destination view layer, on which the animation should appear + + @param rSpriteSizePixel + The overall size of the sprite in device coordinate + space, sufficient to display all transformations, + shape changes and clips. + + @param nSpritePrio + Priority of the sprite. Must remain static over the + lifetime of this object + */ + AnimatedSprite( const ViewLayerSharedPtr& rViewLayer, + const ::basegfx::B2DSize& rSpriteSizePixel, + double nSpritePrio ); + AnimatedSprite(const AnimatedSprite&) = delete; + AnimatedSprite& operator=(const AnimatedSprite&) = delete; + + /** Resize the sprite. + + @param rSpriteSizePixel + The new size in pixel + */ + void resize( const ::basegfx::B2DSize& rSpriteSizePixel ); + + /** Set an offset for the content output in pixel + + This method offsets the output on the sprite content + canvas by the specified amount of device pixel (for + subsequent render operations). + */ + void setPixelOffset( const ::basegfx::B2DSize& rPixelOffset ); + + /// Show the sprite + void show(); + + /// Hide the sprite + void hide(); + + /** Query the content canvas for the current sprite. + + Note that this method must be called + <em>every time</em> something is rendered to the + sprite, because XCustomSprite does not guarantee the + validity of the canvas after a render operation. + + Furthermore, the view transformation on the returned + canvas is already correctly setup, matching the + associated destination canvas. + */ + ::cppcanvas::CanvasSharedPtr getContentCanvas() const; + + /** Move the sprite in device pixel space. + + If the sprite is not yet created, this method has no + effect. + */ + void movePixel( const ::basegfx::B2DPoint& rNewPos ); + + /** Set the alpha value of the sprite. + + If the sprite is not yet created, this method has no + effect. + */ + void setAlpha( double rAlpha ); + + /** Set a sprite clip in user coordinate space. + + If the sprite is not yet created, this method has no + effect. + */ + void clip( const ::basegfx::B2DPolyPolygon& rClip ); + + /** Clears a sprite clip + + If the sprite is not yet created, this method has no + effect. + */ + void clip(); + + /** Set a sprite transformation. + + If the sprite is not yet created, this method has no + effect. + */ + void transform( const ::basegfx::B2DHomMatrix& rTransform ); + + private: + ViewLayerSharedPtr mpViewLayer; + + ::cppcanvas::CustomSpriteSharedPtr mpSprite; + ::basegfx::B2DSize maEffectiveSpriteSizePixel; + ::basegfx::B2DSize maContentPixelOffset; + + double mnSpritePrio; + double mnAlpha; + ::std::optional< ::basegfx::B2DPoint > maPosPixel; + ::std::optional< ::basegfx::B2DPolyPolygon > maClip; + + bool mbSpriteVisible; + }; + + typedef ::std::shared_ptr< AnimatedSprite > AnimatedSpriteSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATEDSPRITE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animation.hxx b/slideshow/source/inc/animation.hxx new file mode 100644 index 000000000..a9264d76b --- /dev/null +++ b/slideshow/source/inc/animation.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_INC_ANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATION_HXX + +#include "animatableshape.hxx" +#include "shapeattributelayer.hxx" +#include "disposable.hxx" + + +/* Definition of Animation interface */ + +namespace slideshow::internal + { + /** Interface defining a generic animation. + + This interface is used by objects implementing the + Activity interface to drive the animation effect. Objects + implementing this interface will receive time-varying + animation values, which they can forward to the + appropriate attributes. The type of these animation values + is given in derived interfaces. + + @see NumberAnimation + @see ColorAnimation + @see PairAnimation + */ + class Animation : public virtual SharedPtrAble + { + public: + /** Notify that the animation going active soon. + + Implementers should preload any buffers, and create + any expensive objects at this time. + + @param rShape + Shape to apply this animation to. + + @param rAttrLayer + Attribute layer to play the animation on. + */ + virtual void prefetch( ) = 0; + + /** Notify that the animation is about to begin. + + Implementers are free to start accompanying effects, + such as sounds, and the animation timer now. + + @param rShape + Shape to apply this animation to. + + @param rAttrLayer + Attribute layer to play the animation on. + */ + virtual void start( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) = 0; + + /** Notify that the animation is about to end. + */ + virtual void end() = 0; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animationactivity.hxx b/slideshow/source/inc/animationactivity.hxx new file mode 100644 index 000000000..20454aa59 --- /dev/null +++ b/slideshow/source/inc/animationactivity.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_INC_ANIMATIONACTIVITY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONACTIVITY_HXX + +#include "activity.hxx" +#include "animatableshape.hxx" +#include "shapeattributelayer.hxx" + + +/* Definition of AnimationActivity interface */ + +namespace slideshow::internal + { + + /** Extends the Activity interface with animation-specific functions + */ + class AnimationActivity : public Activity + { + public: + /** Sets targets (shape and attributeLayer) + + Since attribute layers can only be generated when the + animation starts, the Activity owner must be able to + pass it into the Activity after initial creation. The + same applies to the actual shape the animation must + run for, since e.g. subsetted shapes are generated + close before the animation starts, too (this is not + necessary in and out of itself, but for performance + reasons. Otherwise, character iterations produce tons + of subset shapes). + + @param rShape + Shape to play the animation on. + + @param rAttrLayer + Attribute layer to change the animated values on. + */ + virtual void setTargets( const AnimatableShapeSharedPtr& rShape, + const ShapeAttributeLayerSharedPtr& rAttrLayer ) = 0; + }; + + typedef ::std::shared_ptr< AnimationActivity > AnimationActivitySharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONACTIVITY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animationeventhandler.hxx b/slideshow/source/inc/animationeventhandler.hxx new file mode 100644 index 000000000..0c66ffd5a --- /dev/null +++ b/slideshow/source/inc/animationeventhandler.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONEVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONEVENTHANDLER_HXX + +#include <memory> +#include "animationnode.hxx" + + +/* Definition of AnimationEventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling animation events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle animation state change events. + */ + class AnimationEventHandler + { + public: + virtual ~AnimationEventHandler() {} + + /** Handle the event. + + @param rNode + Animation node which caused this event to fire + + @return true, if this handler has successfully + processed the animation event. When this method + returns false, possibly other, less prioritized + handlers are called, too. + */ + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) = 0; + }; + + typedef ::std::shared_ptr< AnimationEventHandler > AnimationEventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONEVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animationfactory.hxx b/slideshow/source/inc/animationfactory.hxx new file mode 100644 index 000000000..3004c7130 --- /dev/null +++ b/slideshow/source/inc/animationfactory.hxx @@ -0,0 +1,150 @@ +/* -*- 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_INC_ANIMATIONFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONFACTORY_HXX + + +#include "numberanimation.hxx" +#include "enumanimation.hxx" +#include "coloranimation.hxx" +#include "stringanimation.hxx" +#include "boolanimation.hxx" +#include "pairanimation.hxx" + +#include "shapemanager.hxx" + +namespace box2d::utils { typedef ::std::shared_ptr< class box2DWorld > Box2DWorldSharedPtr; } + +/* Definition of AnimationFactory class */ + +namespace slideshow::internal + { + + /** Factory for Animation objects + + Given a SMIL XAnimate node, this factory generates the + appropriate Animation object from that, which will modify + the attribute as specified. + */ + namespace AnimationFactory + { + /** Classifies the attribute name. + + This enum maps names to appropriate factory methods. + */ + enum AttributeClass + { + /// Unknown, prolly invalid name + CLASS_UNKNOWN_PROPERTY, + /// Use createNumberPropertyAnimation + CLASS_NUMBER_PROPERTY, + /// Use createEnumPropertyAnimation + CLASS_ENUM_PROPERTY, + /// Use createColorPropertyAnimation + CLASS_COLOR_PROPERTY, + /// Use createStringPropertyAnimation + CLASS_STRING_PROPERTY, + /// Use createBoolPropertyAnimation + CLASS_BOOL_PROPERTY + }; + + AttributeClass classifyAttributeName( const OUString& rAttrName ); + + /// Collection of flags common to all factory methods + enum FactoryFlags + { + /** Don't call enter/leaveAnimation for the Shape. + + This is useful for set effects + */ + FLAG_NO_SPRITE = 1 + }; + + NumberAnimationSharedPtr createNumberPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags=0 ); + + EnumAnimationSharedPtr createEnumPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ); + + ColorAnimationSharedPtr createColorPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags=0 ); + + /** Create scale or move animation + + @param nTransformType + Must be one of + animations::AnimationTransformType::TRANSLATE or + animations::AnimationTransformType::SCALE. + */ + PairAnimationSharedPtr createPairPropertyAnimation( const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + sal_Int16 nTransformType, + int nFlags ); + + StringAnimationSharedPtr createStringPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ); + + BoolAnimationSharedPtr createBoolPropertyAnimation( const OUString& rAttrName, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ); + + NumberAnimationSharedPtr createPathMotionAnimation( const OUString& rSVGDPath, + sal_Int16 nAdditive, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + const box2d::utils::Box2DWorldSharedPtr& pBox2DWorld, + int nFlags ); + + NumberAnimationSharedPtr 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 ); + } + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animationnode.hxx b/slideshow/source/inc/animationnode.hxx new file mode 100644 index 000000000..14fd21ce9 --- /dev/null +++ b/slideshow/source/inc/animationnode.hxx @@ -0,0 +1,155 @@ +/* -*- 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_INC_ANIMATIONNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONNODE_HXX + +#include "disposable.hxx" + +#include <com/sun/star/animations/XAnimationNode.hpp> +#include <memory> + +namespace slideshow::internal +{ +class AnimationNode; +typedef ::std::shared_ptr<AnimationNode> AnimationNodeSharedPtr; + +/** This interface is used to mirror every XAnimateNode object + in the presentation core. +*/ +class AnimationNode : public Disposable +{ +public: + /** The current state of this AnimationNode + */ + enum NodeState + { + /// Invalid state, node is disposed or otherwise invalid + INVALID = 0, + /// Unresolved start time + UNRESOLVED = 1, + /// Resolved start time, node will start eventually + RESOLVED = 2, + /// Node is active + ACTIVE = 4, + /// Node is frozen (no longer active, but changes remain in place) + FROZEN = 8, + /// Node has completed an active lifecycle, + /// and any effect is removed from the document + ENDED = 16 + }; + + /** Query the corresponding XAnimationNode. + */ + virtual css::uno::Reference<css::animations::XAnimationNode> getXAnimationNode() const = 0; + + /** Init this node + + If this node is not in state INVALID, init() sets up the + node state and schedules necessary events. + If this node has children, they have their init() called, too. + You will call this method whenever a slide is going to be + shown. + + @return true, if init was successful; state has changed to UNRESOLVED + */ + virtual bool init() = 0; + + /** Resolve node start time + + Nodes can have unresolved start times, i.e. indefinite + start time for container nodes, or child nodes whose + parent has not yet started. Calling this method fixes + the node's start time. This does not mean that this + node immediately starts its animations, that is only + the case for begin=0.0. The node will change its state + to RESOLVED. + + @return true, if a start event was successfully scheduled. + */ + virtual bool resolve() = 0; + + /** Immediately start this node + + This method starts the animation on this node, without + begin timeout. The node will change its state to ACTIVE. + */ + virtual void activate() = 0; + + /** Immediately stop this node + + This method stops the animation on this node. The node + will change its state to either ENDED or FROZEN, + depending on XAnimationNode attributes. + */ + virtual void deactivate() = 0; + + /** End the animation on this node + + This method force-ends animation on this node. Parents + may call this for their children, if their active + duration ends. An ended animation will no longer have + any effect on the shape attributes. The node will + change its state to ENDED. + */ + virtual void end() = 0; + + /** Query node state + + @return the current state of this animation node. + */ + virtual NodeState getState() const = 0; + + /** Register a deactivating listener + + This method registers another AnimationNode as an + deactivating listener, which gets notified via a + notifyDeactivating() call. The node calls all + registered listener, when it leaves the ACTIVE state. + + @param rNotifee AnimationNode to notify + */ + virtual bool registerDeactivatingListener(const AnimationNodeSharedPtr& rNotifee) = 0; + + /** Called to notify another AnimationNode's deactivation + + @param rNotifier The instance who calls this method. + */ + virtual void notifyDeactivating(const AnimationNodeSharedPtr& rNotifier) = 0; + + /** Called by the container to remove the animation effect + to make the painted shape correct if it restart because + of repeat or rewind ( fill mode is AnimationFill::REMOVE ) + to start state. + */ + virtual void removeEffect() = 0; + + /** Query node whether it has an animation pending. + + @return true, if this node (or at least one of its children) + has an animation pending. Used to determine if the main + sequence is actually empty, or contains effects + */ + virtual bool hasPendingAnimation() const = 0; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/animationnodefactory.hxx b/slideshow/source/inc/animationnodefactory.hxx new file mode 100644 index 000000000..af424ec50 --- /dev/null +++ b/slideshow/source/inc/animationnodefactory.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONNODEFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONNODEFACTORY_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/animations/XAnimationNode.hpp> + +#include <basegfx/vector/b2dvector.hxx> + +#include "animationnode.hxx" +#include "slideshowcontext.hxx" + + +namespace slideshow::internal::AnimationNodeFactory +{ + /* Definition of AnimationNodeFactory class */ + + /** Create an AnimationNode for the given XAnimationNode + */ + AnimationNodeSharedPtr createAnimationNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode, + const ::basegfx::B2DVector& rSlideSize, + const SlideShowContext& rContext ); + + +#if defined(DBG_UTIL) + void showTree( AnimationNodeSharedPtr const & pRootNode ); +# define SHOW_NODE_TREE(a) AnimationNodeFactory::showTree(a) +#else +# define SHOW_NODE_TREE(a) +#endif + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ANIMATIONNODEFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/attributableshape.hxx b/slideshow/source/inc/attributableshape.hxx new file mode 100644 index 000000000..d903c3b2a --- /dev/null +++ b/slideshow/source/inc/attributableshape.hxx @@ -0,0 +1,220 @@ +/* -*- 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_INC_ATTRIBUTABLESHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ATTRIBUTABLESHAPE_HXX + +#include <memory> + +#include "animatableshape.hxx" +#include "shapeattributelayer.hxx" +#include "doctreenodesupplier.hxx" + +namespace slideshow::internal + { + // forward declaration necessary, because methods use AttributableShapeSharedPtr + class AttributableShape; + + typedef ::std::shared_ptr< AttributableShape > AttributableShapeSharedPtr; + + /** Represents an animatable shape, that can have its + attributes changed. + + Over an animatable shape, this interface adds attribute + modification methods. Furthermore, the shape can be + queried for sub items, which in turn can be separated out + into own AttributableShapes. + */ + class AttributableShape : public AnimatableShape + { + public: + // Attribute layer methods + + + /** Create a new shape attribute layer. + + This method creates a new layer for shape attributes, + which lies atop of all previous attribute layers. That + is most typically used when a new SMIL animation + starts (which according to the spec always lies atop + of all previous animations). Thus, subsequent calls to + this method generate a sandwich of attribute layers, + which in total define the shape's attributes. + + Please note that the attribute layers do <em>not</em> + contain the underlying XShape's attributes as + default. Instead, attributes not explicitly set by + animations remain in invalid state, allowing the + shape's paint method to determine whether they have to + override the underlying graphical shape + representation. XShape attributes must be passed + explicitly to animations which need them (e.g. 'by' + animations). + + @return the new layer + */ + virtual ShapeAttributeLayerSharedPtr createAttributeLayer() = 0; + + /** Revoke a previously generated attribute layer. + + This method revokes a previously generated attribute + layer, and removes the effect of that layer from this + shape. The layer need not be the current toplevel + layer, it can also be revoked from in between. + + @param rLayer + Layer to revoke. Must have been generated by + createAttributeLayer() at the same Shape. + + @return true, if layer was successfully removed, false + otherwise (e.g. if the given layer was not generated + for this shape). + */ + virtual bool revokeAttributeLayer( const ShapeAttributeLayerSharedPtr& rLayer ) = 0; + + /** Get the topmost shape attribute layer (if any). + + This method returns the topmost layer for shape + attributes, i.e. the one which ultimately determines + the shape's look. + + Please note that the attribute layers do <em>not</em> + contain the underlying XShape's attributes as + default. Instead, attributes not explicitly set by + animations remain in invalid state, allowing the + shape's paint method to determine whether they have to + override the underlying graphical shape + representation. XShape attributes must be passed + explicitly to animations which need them (e.g. 'by' + animations). + + @return the topmost layer + */ + virtual ShapeAttributeLayerSharedPtr getTopmostAttributeLayer() const = 0; + + + /** Change default shape visibility + + This method hides or unhides a shape. Note that every + attribute layer generated for this shape is able to + override the setting given here, until it is revoked. + + @param bVisible + When true, shape will be visible, when false, + invisible (modulo attribute layer overrides). + */ + virtual void setVisibility( bool bVisible ) = 0; + + // Sub-item handling + + + /** Retrieve interface for DocTreeNode creation. + + This method provides the caller with a reference to + the DocTreeNodeSupplier interface, which can be used + to request specific tree nodes for this shape. + */ + virtual const DocTreeNodeSupplier& getTreeNodeSupplier() const = 0; + virtual DocTreeNodeSupplier& getTreeNodeSupplier() = 0; + + /** Query the subset this shape displays. + + This method returns a tree node denoting the subset + displayed by this shape. If this shape is not a subset + shape, an empty tree node should be returned. If this + shape is a subset, and itself has subsetted children, + this method might return more than the shape is + actually displaying (because a single DocTreeNode is + not able to model holes in the range). + */ + virtual DocTreeNode getSubsetNode() const = 0; + + /** Query a subset Shape, if already existent at this + object + + This method returns a clone of this Shape, which + renders only the selected subset of itself, but only + if such a subset has been explicitly created before. + + @param rTreeNode + A DocTreeNode instance queried from this Shape, which + specifies the subset of the Shape to render. + + @return a NULL Shape pointer, if no subset exists for + the given DocTreeNode. + */ + virtual AttributableShapeSharedPtr getSubset( const DocTreeNode& rTreeNode ) const = 0; + + /** Create a subset Shape + + This method creates a clone of this Shape, which + renders only the selected subset of itself. Multiple + createSubset() calls for the same DocTreeNode will all + share the same subset shape. + + The original shape (i.e. the one this method is called + on) will cease to display the selected subset + part. That is, together the shapes will display the + original content, but the content of all subset shapes + and their original shape will always be mutually + disjunct. + + After deregistering the subset shape a matching number + of times via revokeSubset(), the original shape will + resume displaying the subsetted part. + + @attention To maintain view integrity, this method + should only be called from the LayerManager + + @param o_rSubset + The requested Shape + + @param rTreeNode + A DocTreeNode instance queried from this Shape, which + specifies the subset of the Shape to render + + @return true, if the shape was newly created, and + false, if an already existing subset is returned. + */ + virtual bool createSubset( AttributableShapeSharedPtr& o_rSubset, + const DocTreeNode& rTreeNode ) = 0; + + /** Revoke a previously generated shape subset. + + After revoking a subset shape, the corresponding + subset part will become visible again on the original + shape. + + @attention To maintain view integrity, this method + should only be called from the LayerManager + + @param rShape + The subset to revoke + + @return true, if the last client called + revokeSubset(). + */ + virtual bool revokeSubset( const AttributableShapeSharedPtr& rShape ) = 0; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ATTRIBUTABLESHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/attributemap.hxx b/slideshow/source/inc/attributemap.hxx new file mode 100644 index 000000000..1cd9b0f30 --- /dev/null +++ b/slideshow/source/inc/attributemap.hxx @@ -0,0 +1,71 @@ +/* -*- 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_INC_ATTRIBUTEMAP_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ATTRIBUTEMAP_HXX + +#include <rtl/ustring.hxx> + +/* Definition of AttributeMap helper function */ + +namespace slideshow::internal + { + /** Type of to-be-animated attribute. + + This enum describes the type of an animated + attribute. + */ + enum class AttributeType + { + Invalid, + CharColor, + CharFontName, + CharHeight, + CharPosture, + CharUnderline, + CharWeight, + Color, + DimColor, + FillColor, + FillStyle, + Height, + LineColor, + LineStyle, + Opacity, + Rotate, + SkewX, + SkewY, + Visibility, + Width, + PosX, + PosY + }; + + /** Map attribute name to AttributeType enum + + @returns AttributeType::Invalid, if name was not found in the + mapping table. + */ + AttributeType mapAttributeName( const OUString& rAttrName ); + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ATTRIBUTEMAP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/basecontainernode.hxx b/slideshow/source/inc/basecontainernode.hxx new file mode 100644 index 000000000..79dbe3f2f --- /dev/null +++ b/slideshow/source/inc/basecontainernode.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_INC_BASECONTAINERNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_BASECONTAINERNODE_HXX + +#include "basenode.hxx" + +namespace slideshow::internal { + +class BaseContainerNode; +typedef ::std::shared_ptr< BaseContainerNode > BaseContainerNodeSharedPtr; + + +/** This interface extends BaseNode with child handling methods. + Used for XAnimationNode objects which have children +*/ +class BaseContainerNode : public BaseNode +{ +public: + BaseContainerNode( + css::uno::Reference<css::animations::XAnimationNode> const& xNode, + BaseContainerNodeSharedPtr const& pParent, + NodeContext const& rContext ); + + /** Add given child node to this container + */ + void appendChildNode( AnimationNodeSharedPtr const& pNode ); + +#if defined(DBG_UTIL) + virtual void showState() const override; + virtual const char* getDescription() const override { return "BaseContainerNode"; } +#endif + +protected: + // overrides from BaseNode + virtual void dispose() override; + +private: + virtual bool init_st() override; + bool init_children(); + virtual void deactivate_st( NodeState eDestState ) override; + virtual bool hasPendingAnimation() const override; + // force to be implemented by derived class: + virtual void activate_st() override = 0; + virtual void notifyDeactivating( + AnimationNodeSharedPtr const& rNotifier ) override = 0; + +protected: + bool isDurationIndefinite() const { return mbDurationIndefinite; } + + bool isChildNode( AnimationNodeSharedPtr const& pNode ) const; + + /// @return true: if all children have been deactivated + bool notifyDeactivatedChild( AnimationNodeSharedPtr const& pChildNode ); + + void repeat(); + + template <typename FuncT> + void forEachChildNode( FuncT func, + int nodeStateMask ) const + { + for (AnimationNodeSharedPtr const& pNode : maChildren) { + if (nodeStateMask != -1 && (pNode->getState() & nodeStateMask) == 0) + continue; + func(pNode); + } + } + + typedef ::std::vector<AnimationNodeSharedPtr> VectorOfNodes; + VectorOfNodes maChildren; + ::std::size_t mnFinishedChildren; + double mnLeftIterations; + +private: + const bool mbRepeatIndefinite; + const bool mbRestart; + const bool mbDurationIndefinite; +}; + +} // namespace presentation::interface + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/basenode.hxx b/slideshow/source/inc/basenode.hxx new file mode 100644 index 000000000..668d9de93 --- /dev/null +++ b/slideshow/source/inc/basenode.hxx @@ -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 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_BASENODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_BASENODE_HXX + +#include <basegfx/vector/b2dvector.hxx> +#include <tools/diagnose_ex.h> +#include <osl/diagnose.hxx> + +#include "event.hxx" +#include "animationnode.hxx" +#include "slideshowcontext.hxx" +#include "shapesubset.hxx" + +#include <vector> + +namespace slideshow::internal { + +/** Context for every node. + + Besides the global AnimationNodeFactory::Context data, + this struct also contains the current DocTree subset + for this node. If start and end index of the + DocTreeNode are equal, the node should use the + complete shape. +*/ +struct NodeContext +{ + NodeContext( const SlideShowContext& rContext, + const ::basegfx::B2DVector& rSlideSize ) + : maContext( rContext ), + maSlideSize( rSlideSize ), + mpMasterShapeSubset(), + mnStartDelay(0.0), + mbIsIndependentSubset( true ) + {} + + /// Context as passed to createAnimationNode() + SlideShowContext maContext; + + /// Size in user coordinate space of the corresponding slide + ::basegfx::B2DVector maSlideSize; + + /// Shape to be used (provided by parent, e.g. for iterations) + ShapeSubsetSharedPtr mpMasterShapeSubset; + + /// Additional delay to node begin (to offset iterate effects) + double mnStartDelay; + + /// When true, subset must be created during slide initialization + bool mbIsIndependentSubset; +}; + +class BaseContainerNode; +typedef ::std::shared_ptr< BaseContainerNode > BaseContainerNodeSharedPtr; + +class BaseNode; +typedef ::std::shared_ptr< BaseNode > BaseNodeSharedPtr; + + +/** This interface extends AnimationNode with some + file-private accessor methods. +*/ +class BaseNode : public AnimationNode, + public ::osl::DebugBase<BaseNode> +{ +public: + BaseNode( css::uno::Reference<css::animations::XAnimationNode> const& xNode, + BaseContainerNodeSharedPtr const& pParent, + NodeContext const& rContext ); + BaseNode(const BaseNode&) = delete; + BaseNode& operator=(const BaseNode&) = delete; + + /** Provide the node with a shared_ptr to itself. + + Since implementation has to create objects which need + a shared_ptr to this node, and a pointee cannot + retrieve a shared_ptr to itself internally, have to + set that from the outside. + */ + void setSelf( const BaseNodeSharedPtr& rSelf ); + + +#if defined(DBG_UTIL) + virtual void showState() const; + virtual const char* getDescription() const; +#endif + + const ::std::shared_ptr< BaseContainerNode >& getParentNode() const + { return mpParent; } + + // Disposable: + virtual void dispose() override; + + // AnimationNode: + virtual bool init() override; + virtual bool resolve() override; + virtual void activate() override; + virtual void deactivate() override; + virtual void end() override; + virtual css::uno::Reference<css::animations::XAnimationNode> getXAnimationNode() const override; + virtual NodeState getState() const override; + virtual bool registerDeactivatingListener( + const AnimationNodeSharedPtr& rNotifee ) override; + // nop: + virtual void notifyDeactivating( const AnimationNodeSharedPtr& rNotifier ) override; + + bool isMainSequenceRootNode() const { return mbIsMainSequenceRootNode; } + + /// Get the node's fill mode + sal_Int16 getFillMode(); + + virtual void removeEffect() override {} +protected: + void scheduleDeactivationEvent( EventSharedPtr const& pEvent = + EventSharedPtr() ); + + SlideShowContext const& getContext() const { return maContext; } + ::std::shared_ptr<BaseNode> const& getSelf() const { return mpSelf; } + + bool checkValidNode() const { + ENSURE_OR_THROW( mpSelf, "no self ptr set!" ); + bool const bRet = (meCurrState != INVALID); + OSL_ENSURE( bRet, "### INVALID node!" ); + return bRet; + } + +private: + // all state affecting methods have "_st" counterparts being called at + // derived classes when in state transition: no-ops here at BaseNode... + virtual bool init_st(); + virtual bool resolve_st(); + virtual void activate_st(); + virtual void deactivate_st( NodeState eDestState ); + +private: + /// notifies + /// - all registered deactivation listeners + /// - single animation end (every node) + /// - slide animations (if main sequence root node) + void notifyEndListeners() const; + + /// Get the node's restart mode + sal_Int16 getRestartMode(); + + /** Get the default restart mode + + If this node's default mode is + AnimationRestart::DEFAULT, this method recursively + calls the parent node. + */ + sal_Int16 getRestartDefaultMode() const; + + /** Get the default fill mode. + + If this node's default mode is AnimationFill::DEFAULT, + this method recursively calls the parent node. + */ + sal_Int16 getFillDefaultMode() const; + + bool isTransition( NodeState eFromState, NodeState eToState, + bool debugAssert = true ) const { + bool const bRet =((mpStateTransitionTable[eFromState] & eToState) != 0); + OSL_ENSURE( !debugAssert || bRet, "### state unreachable!" ); + return bRet; + } + + bool inStateOrTransition( int mask ) const { + return ((meCurrState & mask) != 0 || + (meCurrentStateTransition & mask) != 0); + } + + class StateTransition; + friend class StateTransition; + +private: + SlideShowContext maContext; + + ::std::vector< AnimationNodeSharedPtr > maDeactivatingListeners; + css::uno::Reference< css::animations::XAnimationNode > mxAnimationNode; + ::std::shared_ptr< BaseContainerNode > mpParent; + ::std::shared_ptr< BaseNode > mpSelf; + const int* mpStateTransitionTable; + const double mnStartDelay; + NodeState meCurrState; + int meCurrentStateTransition; + EventSharedPtr mpCurrentEvent; + const bool mbIsMainSequenceRootNode; +}; + +} // namespace slideshow::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/boolanimation.hxx b/slideshow/source/inc/boolanimation.hxx new file mode 100644 index 000000000..79744bc62 --- /dev/null +++ b/slideshow/source/inc/boolanimation.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_INC_BOOLANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_BOOLANIMATION_HXX + +#include "animation.hxx" + + +/* Definition of BoolAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a bool animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes that have + only two discrete values (on and off, or true and false, + for example). + */ + class BoolAnimation : public Animation + { + public: + typedef bool ValueType; + + /** Set the animation to the given value + + @param bValue + Current animation value. + */ + virtual bool operator()( ValueType bValue ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual ValueType getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< BoolAnimation > BoolAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_BOOLANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/box2dtools.hxx b/slideshow/source/inc/box2dtools.hxx new file mode 100644 index 000000000..e876468df --- /dev/null +++ b/slideshow/source/inc/box2dtools.hxx @@ -0,0 +1,483 @@ +/* -*- 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/. + */ + +#pragma once + +#include "shape.hxx" +#include "shapeattributelayer.hxx" +#include "attributemap.hxx" +#include <unordered_map> +#include <queue> + +class b2Body; +class b2World; + +namespace slideshow::internal +{ +class ShapeManager; +typedef std::shared_ptr<ShapeManager> ShapeManagerSharedPtr; +} + +namespace box2d::utils +{ +class box2DBody; +class box2DWorld; +typedef std::shared_ptr<box2DWorld> Box2DWorldSharedPtr; +typedef std::shared_ptr<box2DBody> Box2DBodySharedPtr; + +enum box2DBodyType +{ + BOX2D_STATIC_BODY = 0, + BOX2D_KINEMATIC_BODY, + BOX2D_DYNAMIC_BODY +}; + +enum box2DNonsimulatedShapeUpdateType +{ + BOX2D_UPDATE_POSITION_CHANGE, + BOX2D_UPDATE_POSITION, + BOX2D_UPDATE_ANGLE, + BOX2D_UPDATE_SIZE, + BOX2D_UPDATE_VISIBILITY, + BOX2D_UPDATE_LINEAR_VELOCITY, + BOX2D_UPDATE_ANGULAR_VELOCITY +}; + +/// Holds required information to perform an update to box2d +/// body of a shape that was altered by an animation effect +struct Box2DDynamicUpdateInformation +{ + /// reference to the shape that the update belongs to + css::uno::Reference<css::drawing::XShape> mxShape; + union { + ::basegfx::B2DPoint maPosition; + ::basegfx::B2DVector maVelocity; + double mfAngle; + double mfAngularVelocity; + bool mbVisibility; + }; + box2DNonsimulatedShapeUpdateType meUpdateType; + /// amount of steps to delay the update for + int mnDelayForSteps = 0; +}; + +/** Class that manages the Box2D World + + This class is used when there's a physics animation going on, + it handles the stepping through the box2d world, updating the + shapes in the box2d world if they were changed by ongoing animations. + */ +class box2DWorld +{ +private: + /// Pointer to the real Box2D World that this class manages + std::unique_ptr<b2World> mpBox2DWorld; + /// Scale factor for conversions between LO user space coordinates to Box2D World coordinates + double mfScaleFactor; + bool mbShapesInitialized; + /// Holds whether or not there is a PhysicsAnimation that + /// is stepping the Box2D World. Used to create a lock mechanism + bool mbHasWorldStepper; + /// Flag used to stop overstepping that occurs when a physics + /// animation effect transfers step-lock to another one. + bool mbAlreadyStepped; + /// Number of Physics Animations going on + int mnPhysicsAnimationCounter; + std::unordered_map<css::uno::Reference<css::drawing::XShape>, Box2DBodySharedPtr> + mpXShapeToBodyMap; + + /** Queue that holds any required information to keep LO animation effects + and Box2DWorld in sync + + Is processed before every step of the box2d world by processUpdateQueue. + Holds position, rotation, visibility etc. changes and associated values. + */ + std::queue<Box2DDynamicUpdateInformation> maShapeParallelUpdateQueue; + + /// Creates a static frame in Box2D world that corresponds to the slide borders + void createStaticFrameAroundSlide(const ::basegfx::B2DVector& rSlideSize); + + /** Sets shape's corresponding Box2D body to the specified position + + Body is teleported to the specified position, not moved + + @param xShape + Shape reference + + @param rOutPos + Position in LO user space coordinates + */ + void setShapePosition(const css::uno::Reference<css::drawing::XShape> xShape, + const ::basegfx::B2DPoint& rOutPos); + + /** Moves shape's corresponding Box2D body to specified position + + Moves shape's corresponding Box2D body to specified position as if + the body had velocity to reach that point in given time frame. + + @param xShape + Shape reference + + @param rOutPos + Position in LO user space coordinates + + @param fPassedTime + Time frame which the Box2D body should move to the specified position. + */ + void setShapePositionByLinearVelocity(const css::uno::Reference<css::drawing::XShape> xShape, + const ::basegfx::B2DPoint& rOutPos, + const double fPassedTime); + + /** Sets linear velocity of the shape's corresponding Box2D body + + Moves shape's corresponding Box2D body to specified position as if + the body had velocity to reach that point in given time frame. + + @param xShape + Shape reference + + @param rVelocity + Velocity vector in LO user space coordinates. + */ + void setShapeLinearVelocity(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const basegfx::B2DVector& rVelocity); + + /** Sets rotation angle of the shape's corresponding Box2D body + + @param xShape + Shape reference + + @param fAngle + Angle of rotation in degrees. + */ + void setShapeAngle(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const double fAngle); + + /** Rotates shape's corresponding Box2D body to specified angle + + Rotates the Box2D body to specified angle as if the body + had exact angular velocity to reach that point in given + time frame + + @param xShape + Shape reference + + @param fAngle + Angle of rotation in degrees. + + @param fPassedTime + Time frame which the Box2D body should rotate to the specified angle. + */ + void setShapeAngleByAngularVelocity( + const css::uno::Reference<com::sun::star::drawing::XShape> xShape, const double fAngle, + const double fPassedTime); + + /** Sets angular velocity of the shape's corresponding Box2D body. + + @param xShape + Shape reference + + @param fAngularVelocity + Angular velocity in degrees per second. + */ + void setShapeAngularVelocity(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const double fAngularVelocity); + + /** Sets whether a shape's corresponding Box2D body has collision in the Box2D World or not + + Used for animations that change the visibility of the shape. + + @param xShape + Shape reference + + @param bCanCollide + true if collisions should be enabled for the corresponding Box2D body of this shape + and false if it should be disabled. + */ + void setShapeCollision(const css::uno::Reference<com::sun::star::drawing::XShape> xShape, + const bool bCanCollide); + + /** Process the updates queued in the maShapeParallelUpdateQueue + + Called on each step of the box2DWorld. + + @param fPassedTime + Time frame to process the updates accordingly (needed for proper simulations) + */ + void processUpdateQueue(const double fPassedTime); + + /** Simulate and step through time in the Box2D World + + Used in stepAmount + + @attention fTimeStep should not vary. + */ + void step(const float fTimeStep = 1.0f / 100.0f, const int nVelocityIterations = 6, + const int nPositionIterations = 2); + + /// Queue a rotation update that is simulated as if shape's corresponding box2D body rotated to given angle when processed + void + queueDynamicRotationUpdate(const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const double fAngle); + + /// Queue an angular velocity update that sets the shape's corresponding box2D body angular velocity to the given value when processed + void + queueAngularVelocityUpdate(const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const double fAngularVelocity, const int nDelayForSteps = 0); + + /// Queue an collision update that sets the collision of shape's corresponding box2D body when processed + void queueShapeVisibilityUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const bool bVisibility); + + void queueShapePositionUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const ::basegfx::B2DPoint& rOutPos); + +public: + box2DWorld(const ::basegfx::B2DVector& rSlideSize); + ~box2DWorld(); + + bool initiateWorld(const ::basegfx::B2DVector& rSlideSize); + + /** Simulate and step through a given amount of time in the Box2D World + + @param fPassedTime + Amount of time to step through + + @return Amount of time actually stepped through, since it is possible + to only step through a multiple of fTimeStep + + @attention fTimeStep should not vary. + */ + double stepAmount(const double fPassedTime, const float fTimeStep = 1.0f / 100.0f, + const int nVelocityIterations = 6, const int nPositionIterations = 2); + + /// @return whether shapes in the slide are initialized as Box2D bodies or not + bool shapesInitialized(); + /// @return whether the Box2D World is initialized or not + bool isInitialized() const; + + /** Make the shape's corresponding box2D body a dynamic one. + + A dynamic body will be affected by other bodies and the gravity. + + @param xShape + Shape reference + + @param rStartVelocity + Velocity of the shape after making it dynamic + + @param fDensity + Density of the body that is in kg/m^2 + + @param fBounciness + Bounciness of the body that is usually in between [0,1]. + Even though it could take values that are >1, it is way too chaotic. + + @return box2d body pointer + */ + Box2DBodySharedPtr makeShapeDynamic(const css::uno::Reference<css::drawing::XShape>& xShape, + const basegfx::B2DVector& rStartVelocity, + const double fDensity, const double fBounciness); + + /** Make the Box2D body corresponding to the given shape a static one + + A static body will not be affected by other bodies and the gravity. But will + affect other bodies that are dynamic (will still collide with them but won't + move etc.) + + @param pShape + Pointer to the shape to alter the corresponding Box2D body of + + @return box2d body pointer + */ + Box2DBodySharedPtr makeShapeStatic(const slideshow::internal::ShapeSharedPtr& pShape); + + /** Create a static body that is represented by the shape's geometry + + @return pointer to the box2d body + */ + Box2DBodySharedPtr createStaticBody(const slideshow::internal::ShapeSharedPtr& rShape, + const float fDensity = 1.0f, const float fFriction = 0.3f); + + /// Initiate all the shapes in the current slide in the box2DWorld as static ones + void initiateAllShapesAsStaticBodies( + const slideshow::internal::ShapeManagerSharedPtr& pShapeManager); + + /// @return whether the box2DWorld has a stepper or not + bool hasWorldStepper() const; + + /// Set the flag for whether the box2DWorld has a stepper or not + void setHasWorldStepper(const bool bHasWorldStepper); + + /// Queue a position update that is simulated as if shape's corresponding box2D body moved to given position when processed + void queueDynamicPositionUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const ::basegfx::B2DPoint& rOutPos); + + /// Queue a update that sets the corresponding box2D body's linear velocity to the given value when processed + void queueLinearVelocityUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const ::basegfx::B2DVector& rVelocity, + const int nDelayForSteps = 0); + + /// Queue an appropriate update for the animation effect that is in parallel with a physics animation + void + queueShapeAnimationUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, + const slideshow::internal::AttributeType eAttrType, + const bool bIsFirstUpdate); + + /// Queue an appropriate update for a path animation that is in parallel with a physics animation + void queueShapePathAnimationUpdate( + const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, + const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, + const bool bIsFirstUpdate); + + /// Queue an appropriate update for the animation effect that just ended + void queueShapeAnimationEndUpdate(const css::uno::Reference<css::drawing::XShape>& xShape, + const slideshow::internal::AttributeType eAttrType); + + /** Alert that a physics animation effect has ended + + Makes the given shape static, if this was the last physics animation effect + that was in parallel, box2d bodies that are owned by the mpXShapeToBodyMap + are dumped and potentially destroyed. + + @attention the box2d body owned by the PhysicsAnimation won't be destroyed. + */ + void alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape); + + /** Alert that a physics animation effect has started + + Initiates the box2D world if it is not initiated yet, and likewise constructs + box2d bodies for the shapes in the current slide if they are not constructed. + */ + void + alertPhysicsAnimationStart(const ::basegfx::B2DVector& rSlideSize, + const slideshow::internal::ShapeManagerSharedPtr& pShapeManager); +}; + +/// Class that manages a single box2D Body +class box2DBody +{ +private: + /// Pointer to the body that this class manages + std::shared_ptr<b2Body> mpBox2DBody; + /// Scale factor for conversions between LO user space coordinates to Box2D World coordinates + double mfScaleFactor; + +public: + box2DBody(std::shared_ptr<b2Body> pBox2DBody, double fScaleFactor); + + /// @return current position in LO user space coordinates + ::basegfx::B2DPoint getPosition() const; + + /** Set the position of box2d body + + @param rPos + Position in LO user space coordinates + */ + void setPosition(const ::basegfx::B2DPoint& rPos); + + /** Moves body to the specified position + + Moves body to the specified position by setting velocity of + the body so that it reaches to rDesiredPos in given time fram + + @param rDesiredPos + Position to arrive in the time frame + + @param fPassedTime + Amount of time for the movement to take place + */ + void setPositionByLinearVelocity(const ::basegfx::B2DPoint& rDesiredPos, + const double fPassedTime); + + /** Sets linear velocity of the body + + @param rVelocity + Velocity vector in LO user space coordinates + */ + void setLinearVelocity(const ::basegfx::B2DVector& rVelocity); + + /** Rotate body to specified angle of rotation + + Rotates body to specified rotation as if the body had + angular velocity to reach that state in given time frame + + @param fDesiredAngle + Rotation angle in degrees to arrive in the time frame + + @param fPassedTime + Amount of time for the movement to take place + */ + void setAngleByAngularVelocity(const double fDesiredAngle, const double fPassedTime); + + /** Sets angular velocity of the body + + @param fAngularVelocity + Angular velocity in degrees per second + */ + void setAngularVelocity(const double fAngularVelocity); + + /// Sets whether the body have collisions or not + void setCollision(const bool bCanCollide); + + /// @return current angle of rotation of the body + double getAngle() const; + + /** Set angle of the box2d body + + @param fAngle + Angle in degrees + */ + void setAngle(const double fAngle); + + /** Set density and restitution of the box2d body + + @param fDensity + Density in kg/m^2 + + @param fRestitution + Restitution (elasticity) coefficient, usually in the range [0,1] + */ + void setDensityAndRestitution(const double fDensity, const double fRestitution); + + /** Set restitution of the box2d body + + @param fRestitution + Restitution (elasticity) coefficient, usually in the range [0,1] + */ + void setRestitution(const double fRestitution); + + /// Set type of the body + void setType(box2DBodyType eType); + + /// @return type of the body + box2DBodyType getType() const; +}; + +/** Make the Box2D body a dynamic one + + A dynamic body will be affected by other bodies and the gravity. + + @param pBox2DBody + Pointer to the Box2D body + */ +Box2DBodySharedPtr makeBodyDynamic(const Box2DBodySharedPtr& pBox2DBody); + +/** Make the Box2D body a static one + + A static body will not be affected by other bodies and the gravity. + + @param pBox2DBody + Pointer to the Box2D body + */ +Box2DBodySharedPtr makeBodyStatic(const Box2DBodySharedPtr& pBox2DBody); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/slideshow/source/inc/coloranimation.hxx b/slideshow/source/inc/coloranimation.hxx new file mode 100644 index 000000000..21105166b --- /dev/null +++ b/slideshow/source/inc/coloranimation.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_INC_COLORANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_COLORANIMATION_HXX + +#include "animation.hxx" +#include "rgbcolor.hxx" + + +/* Definition of ColorAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a color animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a color value. + */ + class ColorAnimation : public Animation + { + public: + typedef RGBColor ValueType; + + /** Set the animation to the given color value + + @param rColor + Current animation value. + */ + virtual bool operator()( const ValueType& rColor ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual ValueType getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< ColorAnimation > ColorAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_COLORANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/cursormanager.hxx b/slideshow/source/inc/cursormanager.hxx new file mode 100644 index 000000000..cfd5f0a74 --- /dev/null +++ b/slideshow/source/inc/cursormanager.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_CURSORMANAGER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_CURSORMANAGER_HXX + +#include <sal/types.h> + + +/* Definition of CursorManager interface */ + +namespace slideshow::internal + { + + /** Interface for handling the view cursor. + + Classes implementing this interface interact with the + View, arbitrating access to the mouse cursor shape. + */ + class CursorManager + { + public: + virtual ~CursorManager() {} + + /** Request different cursor shape. + + @param nCursorShape + Shape ID of the new mouse cursor + */ + virtual bool requestCursor( sal_Int16 nCursorShape ) = 0; + + /** Reset cursor to default one. + + This method resets the cursor to whatever default to + manager deems appropriate. + */ + virtual void resetCursor() = 0; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_CURSORMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/delayevent.hxx b/slideshow/source/inc/delayevent.hxx new file mode 100644 index 000000000..631553915 --- /dev/null +++ b/slideshow/source/inc/delayevent.hxx @@ -0,0 +1,140 @@ +/* -*- 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_INC_DELAYEVENT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_DELAYEVENT_HXX + +#include "event.hxx" + +#include <functional> + +namespace slideshow::internal { + +/** Event, which delays the functor call the given amount of time + */ +class Delay : public Event +{ +public: + typedef ::std::function<void ()> FunctorT; + + template <typename FuncT> + Delay( FuncT const& func, + double nTimeout + , const OUString& rsDescription + ) : Event(rsDescription), + mnTimeout(nTimeout), maFunc(func), mbWasFired(false) {} + + Delay( const std::function<void ()>& func, + double nTimeout + , const OUString& rsDescription + ) : Event(rsDescription), + mnTimeout(nTimeout), + maFunc(func), + mbWasFired(false) {} + Delay(const Delay&) = delete; + Delay& operator=(const Delay&) = delete; + + // Event: + virtual bool fire() override; + virtual bool isCharged() const override; + virtual double getActivationTime( double nCurrentTime ) const override; + // Disposable: + virtual void dispose() override; + +private: + double const mnTimeout; + FunctorT maFunc; + bool mbWasFired; +}; + +#if OSL_DEBUG_LEVEL <= 1 + +/** Generate delay event + + @param func + Functor to call when the event fires. + + @param nTimeout + Timeout in seconds, to wait until functor is called. + + @return generated delay event +*/ +template <typename FuncT> +inline EventSharedPtr makeDelay_( FuncT const& func, double nTimeout, OUString const& rsDescription ) +{ + return std::make_shared<Delay>( func, nTimeout, rsDescription ); +} + +/** Generate immediate event + + @param func + Functor to call when the event fires. + + @return generated immediate event. +*/ +template <typename FuncT> +inline EventSharedPtr makeEvent_( FuncT const& func, OUString const& rsDescription) +{ + return std::make_shared<Delay>( func, 0.0, rsDescription ); +} + + +#define makeDelay(f, t, d) makeDelay_(f, t, d) +#define makeEvent(f, d) makeEvent_(f, d) + +#else // OSL_DEBUG_LEVEL > 0 + +class Delay_ : public Delay { +public: + template <typename FuncT> + Delay_( FuncT const& func, double nTimeout, + char const* from_function, char const* from_file, int from_line, + const OUString& rsDescription) + : Delay(func, nTimeout, rsDescription), + FROM_FUNCTION(from_function), + FROM_FILE(from_file), FROM_LINE(from_line) {} + + char const* const FROM_FUNCTION; + char const* const FROM_FILE; + int const FROM_LINE; +}; + +template <typename FuncT> +inline EventSharedPtr makeDelay_( + FuncT const& func, double nTimeout, + char const* from_function, char const* from_file, int from_line, + const OUString& rsDescription) +{ + return EventSharedPtr( new Delay_( func, nTimeout, + from_function, from_file, from_line, rsDescription) ); +} + +#define makeDelay(f, t, d) makeDelay_(f, t, \ + __func__, __FILE__, __LINE__, \ + d) +#define makeEvent(f, d) makeDelay_(f, 0.0, \ + __func__, __FILE__, __LINE__, \ + d) + +#endif // OSL_DEBUG_LEVEL <= 1 + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_DELAYEVENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/disposable.hxx b/slideshow/source/inc/disposable.hxx new file mode 100644 index 000000000..a345d76e0 --- /dev/null +++ b/slideshow/source/inc/disposable.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_INC_DISPOSABLE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_DISPOSABLE_HXX + +#include <memory> + + +/* Definition of Disposable interface */ + +namespace slideshow::internal + { + /** + * Base class for being a shared pointer, since quite a few of the downstream classes + * want to be stored using std::shared_ptr. + */ + class SharedPtrAble : public std::enable_shared_from_this<SharedPtrAble> + { + public: + virtual ~SharedPtrAble() {} + }; + + /** Disposable interface + + With ref-counted objects, deleting object networks + containing cycles requires a dispose() call, to enforce + every object to call dispose on and release local + references. + */ + class Disposable + { + public: + virtual ~Disposable() {} + + /** Dispose all object references. + + An implementor of this method must first call + dispose() on any of its external references, and + release them after that. + */ + virtual void dispose() = 0; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_DISPOSABLE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/doctreenode.hxx b/slideshow/source/inc/doctreenode.hxx new file mode 100644 index 000000000..45bbf77b0 --- /dev/null +++ b/slideshow/source/inc/doctreenode.hxx @@ -0,0 +1,111 @@ +/* -*- 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_INC_DOCTREENODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_DOCTREENODE_HXX + +#include <sal/types.h> +#include <vector> + + +/* Definition of DocTreeNode class */ + +namespace slideshow::internal + { + + /** This class represents kind of a DOM tree node for shape + text + + In order to animate subsets of shape text, we need + information about the logical and formatting structure of + that text (lines, paragraphs, words etc.). This is + represented in a tree structure, with DocTreeNodes as the + nodes. Instances of this class can be queried from the + DocTreeNodeSupplier interface. + + This class has nothing to do with the Draw document tree. + */ + class DocTreeNode + { + public: + /// Type of shape entity represented by this node + enum class NodeType + { + /// This node represents a paragraph + LogicalParagraph=129, + /// This node represents a word + LogicalWord=131, + /// This node represents a character + LogicalCharacterCell=132 + }; + + /** Create empty tree node + */ + DocTreeNode() : + mnStartIndex(-1), + mnEndIndex(-1) + { + } + + /** Create tree node from start and end index. + + Create a tree node for the given range and type. + + @param nStartIndex + Start index + + @param nEndIndex + End index (exclusive) + + @param eType + Node type + */ + DocTreeNode( sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) : + mnStartIndex(nStartIndex), + mnEndIndex(nEndIndex) + { + } + + bool isEmpty() const { return mnStartIndex == mnEndIndex; } + + sal_Int32 getStartIndex() const { return mnStartIndex; } + void setStartIndex( sal_Int32 nIndex ) { mnStartIndex = nIndex; } + sal_Int32 getEndIndex() const { return mnEndIndex; } + void setEndIndex( sal_Int32 nIndex ) { mnEndIndex = nIndex; } + + void reset() + { + mnStartIndex = -1; + mnEndIndex = -1; + } + + private: + sal_Int32 mnStartIndex; + sal_Int32 mnEndIndex; + + }; + + typedef ::std::vector< DocTreeNode > VectorOfDocTreeNodes; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_DOCTREENODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/doctreenodesupplier.hxx b/slideshow/source/inc/doctreenodesupplier.hxx new file mode 100644 index 000000000..5bf91d126 --- /dev/null +++ b/slideshow/source/inc/doctreenodesupplier.hxx @@ -0,0 +1,145 @@ +/* -*- 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_INC_DOCTREENODESUPPLIER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_DOCTREENODESUPPLIER_HXX + +#include "doctreenode.hxx" + + +/* Definition of DocTreeNodeSupplier interface */ + +namespace slideshow::internal + { + /** Interface to retrieve DocTreeNodes from subsettable + shapes. + + Shapes which implement the AttributableShape interface + also provides this interface, providing methods to + retrieve specific DocTreeNode objects from the shape. The + methods mainly distinguish various ways on how to specify + the actual DocTreeNode to return. + + If a requested DocTreeNode is not available when one of + the methods below is called, an empty DocTreeNode will be + returned (the predicate DocTreeNode::isEmpty() will return + true). If, on the other hand, the shape cannot determine, + for internal reasons, the internal tree node structure, + all those methods will throw an + ShapeLoadFailedException. This is, in fact, a delayed error + that could also have been reported during shape + construction, but might be postponed until the missing + information is actually requested. + */ + class DocTreeNodeSupplier + { + public: + /** Query number of tree nodes of the given type this + shape contains. + + The value returned by this method minus one is the + maximum value permissible at the getTreeNode() + method, for the given node type. + + @throws ShapeLoadFailedException, if tree node structure + cannot be determined. + */ + virtual sal_Int32 getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const = 0; // throw ShapeLoadFailedException; + + /** Create DocTreeNode from shape. + + This method creates a DocTreeNode from a shape, a + given node type and a running index into the shape's + DocTreeNodes of the given type. + + @param nNodeIndex + Starting with 0, every DocTreeNode of the shape that + has type eNodeType is indexed. The DocTreeNode whose + index equals nNodeIndex will be returned. + + @param eNodeType + Type of the node to return + + @return the DocTreeNode found, or the empty + DocTreeNode, if nothing was found. + + @throws ShapeLoadFailedException, if tree node structure + cannot be determined. + */ + virtual DocTreeNode getTreeNode( sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const = 0; // throw ShapeLoadFailedException; + + /** Query number of tree nodes of the given type this + subset contains. + + The value returned by this method minus one is the + maximum value permissible at the + getSubsetTreeNode() method, for the given node + type. + + @param rParentNode + The parent node, below which the number of tree nodes + of the given type shall be counted. + + @param eNodeType + Node type to count. + + @throws ShapeLoadFailedException, if tree node structure + cannot be determined. + */ + virtual sal_Int32 getNumberOfSubsetTreeNodes( const DocTreeNode& rParentNode, + DocTreeNode::NodeType eNodeType ) const = 0; // throw ShapeLoadFailedException; + + /** Create DocTreeNode from shape subset. + + This method creates a DocTreeNode from a shape, a + parent tree node, a given node type and a running + index into the shape's DocTreeNodes of the given type. + + @param rParentNode + Parent node, below which the tree node with the given + type shall be selected. + + @param nNodeIndex + Starting with 0, every DocTreeNode of the shape that + has type eNodeType is indexed. The DocTreeNode whose + index equals nNodeIndex will be returned. + + @param eNodeType + Type of the node to return + + @return the DocTreeNode found, or the empty + DocTreeNode, if nothing was found. + + @throws ShapeLoadFailedException, if tree node structure + cannot be determined. + */ + virtual DocTreeNode getSubsetTreeNode( const DocTreeNode& rParentNode, + sal_Int32 nNodeIndex, + DocTreeNode::NodeType eNodeType ) const = 0; // throw ShapeLoadFailedException; + + protected: + ~DocTreeNodeSupplier() {} + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_DOCTREENODESUPPLIER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/enumanimation.hxx b/slideshow/source/inc/enumanimation.hxx new file mode 100644 index 000000000..39a8a0251 --- /dev/null +++ b/slideshow/source/inc/enumanimation.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_ENUMANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_ENUMANIMATION_HXX + +#include "animation.hxx" + + +/* Definition of EnumAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining an enum animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a set of constant values, such as UNO constants, or enums. + */ + class EnumAnimation : public Animation + { + public: + typedef sal_Int16 ValueType; + + /** Set the animation to value k + + @param k + Current animation value (must be in an + attribute-specific permissible range). Overflowing + values will be clipped to the permissible range + internally. + */ + virtual bool operator()( ValueType k ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual ValueType getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< EnumAnimation > EnumAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_ENUMANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/event.hxx b/slideshow/source/inc/event.hxx new file mode 100644 index 000000000..9377bc9d7 --- /dev/null +++ b/slideshow/source/inc/event.hxx @@ -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 . + */ +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_EVENT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EVENT_HXX + +#include "disposable.hxx" +#include <rtl/ustring.hxx> +#include <memory> +#include <vector> + +namespace slideshow::internal +{ +/** Definition of Event interface + */ +class Event : public Disposable +{ +public: + Event(const OUString& rsDescription) + : msDescription(rsDescription) + { + } + + /** Execute the event. + + @return true, if event was successfully executed. + */ + virtual bool fire() = 0; + + /** Query whether this event is still charged, i.e. able + to fire. + + Inactive events are ignored by the normal event + containers (EventQueue, UserEventQueue etc.), and no + explicit fire() is called upon them. + + @return true, if this event has already been fired. + */ + virtual bool isCharged() const = 0; + + /** Query the activation time instant this event shall be + fired, if it was inserted at instant nCurrentTime into + the queue. + + @param nCurrentTime + The time from which the activation time is to be + calculated from. + + @return the time instant in seconds, on which this + event is to be fired. + */ + virtual double getActivationTime(double nCurrentTime) const = 0; + + const OUString& GetDescription() const { return msDescription; } + +private: + const OUString msDescription; +}; + +typedef ::std::shared_ptr<Event> EventSharedPtr; +typedef ::std::vector<EventSharedPtr> VectorOfEvents; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EVENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/eventhandler.hxx b/slideshow/source/inc/eventhandler.hxx new file mode 100644 index 000000000..d74b13ecd --- /dev/null +++ b/slideshow/source/inc/eventhandler.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_EVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EVENTHANDLER_HXX + +#include <memory> + + +/* Definition of EventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for event handling objects. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle events. + */ + class EventHandler + { + public: + virtual ~EventHandler() {} + + /** Handle the event. + + @return true, if this handler has successfully + processed the event. When this method returns false, + possibly other, less prioritized handlers are called, + too. + */ + virtual bool handleEvent() = 0; + }; + + typedef ::std::shared_ptr< EventHandler > EventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/eventmultiplexer.hxx b/slideshow/source/inc/eventmultiplexer.hxx new file mode 100644 index 000000000..e0dd6fd31 --- /dev/null +++ b/slideshow/source/inc/eventmultiplexer.hxx @@ -0,0 +1,659 @@ +/* -*- 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_INC_EVENTMULTIPLEXER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EVENTMULTIPLEXER_HXX + +#include "eventhandler.hxx" +#include "mouseeventhandler.hxx" +#include "animationeventhandler.hxx" +#include "pauseeventhandler.hxx" +#include "shapelistenereventhandler.hxx" +#include "vieweventhandler.hxx" + +#include <memory> +#include <com/sun/star/uno/Reference.hxx> + +#include "unoview.hxx" + +namespace com::sun::star::drawing { class XShape; } + +namespace slideshow::internal { + +class EventQueue; +class UnoViewContainer; +class AnimationNode; + +struct EventMultiplexerImpl; + +class RGBColor; + +/** Interface for handling view repaint events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle view repaint events. +*/ +class ViewRepaintHandler +{ +public: + virtual ~ViewRepaintHandler() {} + + /** Notify clobbered view. + + Reasons for a viewChanged notification can be + different view size, transformation, or other device + properties (color resolution or profile, etc.) + + @param rView + The changed view + */ + virtual void viewClobbered( const UnoViewSharedPtr& rView ) = 0; +}; + +typedef ::std::shared_ptr< ViewRepaintHandler > ViewRepaintHandlerSharedPtr; + +/** Interface for handling hyperlink clicks. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle hyperlink events. + */ +class HyperlinkHandler +{ +public: + /** Handle the event. + + @param rLink + The actual hyperlink URI + + @return true, if this handler has successfully + processed the event. When this method returns false, + possibly other, less prioritized handlers are called, + too. + */ + virtual bool handleHyperlink( OUString const& rLink ) = 0; + +protected: + ~HyperlinkHandler() {} +}; + +typedef ::std::shared_ptr< HyperlinkHandler > HyperlinkHandlerSharedPtr; + +/** Interface for handling user paint state changes. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle user paint events. +*/ +class UserPaintEventHandler +{ +public: + virtual ~UserPaintEventHandler() {} + virtual bool colorChanged( RGBColor const& rUserColor ) = 0; + virtual bool widthChanged( double nUserStrokeWidth ) = 0; + virtual bool eraseAllInkChanged(bool bEraseAllInk) =0; + virtual bool eraseInkWidthChanged(sal_Int32 rEraseInkSize) =0; + virtual bool switchEraserMode() = 0; + virtual bool switchPenMode() = 0; + virtual bool disable() = 0; +}; + +typedef ::std::shared_ptr< UserPaintEventHandler > UserPaintEventHandlerSharedPtr; + +/** This class multiplexes user-activated and + slide-show global events. + + This class listens at the XSlideShowView and fires events + registered for certain user actions. Furthermore, global + slide show state changes (such as start or end of a slide) + are handled as well. Note that registered events which + have a non-zero timeout (i.e. events that return non-zero + from getActivationTime()) will not be fired immediately + after the user action occurred, but only after the given + timeout. Which is actually a feature. +*/ +class EventMultiplexer +{ +public: + /** Create an event multiplexer + + @param rEventQueue + Reference to the main event queue. Since we hold this + object by plain reference, it must live longer than we + do. On the other hand, that queue must not fire events + after this object is destroyed, since we might + schedule events there which itself contain plain + references to this object. Basically, EventQueue and + EventMultiplexer should have the same lifetime, and since + this is not possible, both must be destructed in a + phased mode: first clear both of any remaining events, + then destruct them. + + @param rViewContainer + Globally managed list of all registered views. Used to + determine event sources, and for registering view listeners + at. + */ + EventMultiplexer( EventQueue& rEventQueue, + UnoViewContainer const& rViewContainer ); + ~EventMultiplexer(); + EventMultiplexer(const EventMultiplexer&) = delete; + EventMultiplexer& operator=(const EventMultiplexer&) = delete; + + // Management methods + + + /** Clear all registered handlers. + */ + void clear(); + + + // Automatic mode methods + + + /** Change automatic mode. + + @param bIsAuto + When true, events will be fired automatically, not + only triggered by UI events. When false, auto events + will quit. + */ + void setAutomaticMode( bool bIsAuto ); + + /** Get automatic mode setting. + */ + bool getAutomaticMode() const; + + /** Set the timeout for automatic mode. + + @param nTimeout + Timeout, between end of effect until start of next + effect. + */ + void setAutomaticTimeout( double nTimeout ); + + /** Get automatic mode timeout value. + */ + double getAutomaticTimeout() const; + + // Handler registration methods + + + /** Register an event handler that will be called when views are + changed. + + For each view added, viewAdded() will be called on the + handler. For each view removed, viewRemoved() will be + called. Each modified view will cause a viewChanged() call on + each handler. + + You don't need to deregister the handler, it will be + automatically removed, once the pointee becomes stale. + + @param rHandler + Handler to call. + */ + void addViewHandler( const ViewEventHandlerWeakPtr& rHandler ); + void removeViewHandler( const ViewEventHandlerWeakPtr& rHandler ); + + /** Register an event handler that will be called when a view gets + clobbered. + + Note that <em>all</em> registered handlers will be called when + the event. This is in contrast to the mouse events below. + + @param rHandler + Handler to call when a view needs a repaint + */ + void addViewRepaintHandler( const ViewRepaintHandlerSharedPtr& rHandler ); + void removeViewRepaintHandler( const ViewRepaintHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when + XShapeListeners are changed. + + @param rHandler + Handler to call when a shape listener changes + */ + void addShapeListenerHandler( const ShapeListenerEventHandlerSharedPtr& rHandler ); + void removeShapeListenerHandler( const ShapeListenerEventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when + user paint parameters change. + + @param rHandler + Handler to call when a shape listener changes + */ + void addUserPaintHandler( const UserPaintEventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when the + user requests the next effect. + + For every nextEffect event, only one of the handlers + registered here is called. The handlers are considered + with decreasing priority, i.e. the handler with the + currently highest priority will be called. + + @param rHandler + Handler to call when the next effect should start + + @param nPriority + Priority with which the handlers are called. The + higher the priority, the earlier this handler will be + tried. + */ + void addNextEffectHandler( const EventHandlerSharedPtr& rHandler, + double nPriority ); + void removeNextEffectHandler( const EventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when the + slide is just shown. + + Note that <em>all</em> registered handlers will be called + when the slide start occurs. This is in contrast to + the mouse events below. + + @param rHandler + Handler to call when the next slide starts + */ + void addSlideStartHandler( const EventHandlerSharedPtr& rHandler ); + void removeSlideStartHandler( const EventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when the + slide is about to vanish. + + Note that <em>all</em> registered handlers will be + called when the slide end occurs. This is in contrast + to the mouse events below. + + @param rHandler + Handler to call when the current slide ends + */ + void addSlideEndHandler( const EventHandlerSharedPtr& rHandler ); + void removeSlideEndHandler( const EventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when an + XAnimationNode starts its active duration. + + Note that <em>all</em> registered handlers will be called + when the animation start occurs. This is in contrast to + the mouse events below. + + @param rHandler + Handler to call when the animation start + */ + void addAnimationStartHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + void removeAnimationStartHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when an + XAnimationNode ends its active duration. + + Note that <em>all</em> registered handlers will be called + when the animation end occurs. This is in contrast to + the mouse events below. + + @param rHandler + Handler to call when the animation ends + */ + void addAnimationEndHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + void removeAnimationEndHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when the + main animation sequence of a slide ends its active + duration. + + Note that <em>all</em> registered handlers will be + called when the animation end occurs. This is in + contrast to the mouse events below. + + @param rHandler + Handler to call when the animation ends + */ + void addSlideAnimationsEndHandler( + const EventHandlerSharedPtr& rHandler ); + void removeSlideAnimationsEndHandler( + const EventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when an + XAudio node's sound stops playing. + + Note that <em>all</em> registered handlers will be + called when the audio stops. This is in contrast to + the mouse events below. + + @param rHandler + Handler to call when the audio stops + */ + void addAudioStoppedHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + void removeAudioStoppedHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + + /** Register an event handler that will be called when an + XCommand node's with the command STOPAUDIO is activated. + + Note that <em>all</em> registered handlers will be + called when the audio stops. This is in contrast to + the mouse events below. + + @param rHandler + Handler to call when command is activated + */ + void addCommandStopAudioHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + void removeCommandStopAudioHandler( + const AnimationEventHandlerSharedPtr& rHandler ); + + /** Register a handler that is called when the show enters + or exits pause mode. + */ + void addPauseHandler( const PauseEventHandlerSharedPtr& rHandler ); + void removePauseHandler( const PauseEventHandlerSharedPtr& rHandler ); + + /** Register a mouse handler that is called on mouse click + + For every mouse click, only one of the handlers + registered here is called. The handlers are considered + with decreasing priority, i.e. the handler with the + currently highest priority will be called. + + Since the handlers can reject down and up events + individually, handlers should expect to be called with + non-matching down and up-press counts. If your handler + cannot cope with that, it must have the highest + priority of all added handlers. + */ + void addClickHandler( const MouseEventHandlerSharedPtr& rHandler, + double nPriority ); + void removeClickHandler( const MouseEventHandlerSharedPtr& rHandler ); + + /** Register a mouse handler that is called on a double + mouse click + + For every mouse double click, only one of the handlers + registered here is called. The handlers are considered + with decreasing priority, i.e. the handler with the + currently highest priority will be called. + + Since the handlers can reject down and up events + individually, handlers should expect to be called with + non-matching down and up-press counts. If your handler + cannot cope with that, it must have the highest + priority of all added handlers. + */ + void addDoubleClickHandler( const MouseEventHandlerSharedPtr& rHandler, + double nPriority ); + void removeDoubleClickHandler( const MouseEventHandlerSharedPtr& rHandler ); + + /** Register a mouse handler that is called for mouse moves. + + For every mouse move, only one of the handlers + registered here is called. The handlers are considered + with decreasing priority, i.e. the handler with the + currently highest priority will be called. + */ + void addMouseMoveHandler( const MouseEventHandlerSharedPtr& rHandler, + double nPriority ); + void removeMouseMoveHandler( const MouseEventHandlerSharedPtr& rHandler ); + + + /** Registers a hyperlink click handler. + + For every hyperlink click, only one of the handlers registered + here is called. The handlers are considered with decreasing + priority, i.e. the handler with the currently highest priority + will be called. + + @param rHandler + @param nPriority + */ + void addHyperlinkHandler( const HyperlinkHandlerSharedPtr& rHandler, + double nPriority ); + void removeHyperlinkHandler( const HyperlinkHandlerSharedPtr& rHandler ); + + + // External event notifications + + + /** View added. + + This method adds another view, which the show is + displayed on. On every added view, the EventMultiplexer + registers mouse and motion event listeners. + */ + void notifyViewAdded( const UnoViewSharedPtr& rView ); + + /** View removed + + This method removes a view. Registered mouse and + motion event listeners are revoked. + */ + void notifyViewRemoved( const UnoViewSharedPtr& rView ); + + /** View changed + + This method announces a changed view to all view + listeners. View changes include size and transformation. + + @param rView + View that has changed + */ + void notifyViewChanged( const UnoViewSharedPtr& rView ); + + /** View changed + + This method announces a changed view to all view + listeners. View changes include size and transformation. + + @param xView + View that has changed + */ + void notifyViewChanged( const css::uno::Reference<css::presentation::XSlideShowView>& xView ); + + /** All Views changed + + This method announces to all view listeners that + <em>every</em> known view has changed. View changes include + size and transformation. + */ + void notifyViewsChanged(); + + /** View clobbered + + This method announces that the given view has been clobbered + by something external to the slideshow, and needs an update. + + @param xView + View that has been clobbered + */ + void notifyViewClobbered( const css::uno::Reference<css::presentation::XSlideShowView>& xView ); + + /** New shape event listener added + + This method announces that the given listener was added for + the specified shape. + */ + void notifyShapeListenerAdded( const css::uno::Reference<css::drawing::XShape>& xShape ); + + /** A shape event listener was removed + + This method announces that the given listener was removed for + the specified shape. + */ + void notifyShapeListenerRemoved( const css::uno::Reference<css::drawing::XShape>& xShape ); + + /** Notify a new user paint color + + Sending this notification also implies that user paint is + enabled. User paint denotes the feature to draw colored lines + on top of the slide content. + */ + void notifyUserPaintColor( RGBColor const& rUserColor ); + + /** Notify a new user paint width + + Sending this notification also implies that user paint is + enabled. . + */ + void notifyUserPaintStrokeWidth( double rUserStrokeWidth ); + + + /** Notify a new user paint erase all ink mode + + Sending this notification also implies that user paint is + enabled. User paint denotes the feature to draw colored lines + on top of the slide content. + */ + void notifyEraseAllInk( bool bEraseAllInk ); + void notifySwitchPenMode(); + void notifySwitchEraserMode(); + void notifyEraseInkWidth( sal_Int32 rEraseInkSize ); + + /** Notify that user paint is disabled + + User paint denotes the feature to draw colored lines on top of + the slide content. + */ + void notifyUserPaintDisabled(); + + /** Notify that the user requested the next effect. + + This requests the slideshow to display the next + effect, or move to the next slide, if none are left. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifyNextEffect(); + + /** Notify that a new slide has started + + This method is to be used from the Presentation object + to signal that a new slide is starting now. This will + invoke all registered slide start handlers. + */ + void notifySlideStartEvent(); + + /** Notify that a slide has ended + + This method is to be used from the Presentation object + to signal that a slide is ending now. This will invoke + all registered slide end handlers. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifySlideEndEvent(); + + /** Notify that the given node enters its active duration. + + This method is to be used from the AnimationNode + objects to signal that the active duration + begins. This will invoke all registered animation + start handlers. + + @param rNode + Node which enters active duration. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifyAnimationStart( const AnimationNodeSharedPtr& rNode ); + + /** Notify that the given node leaves its active duration. + + This method is to be used from the AnimationNode + objects to signal that the active duration + ends now. This will invoke all registered animation + end handlers. + + @param rNode + Node which leaves active duration. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifyAnimationEnd( const AnimationNodeSharedPtr& rNode ); + + /** Notify that the slide animations sequence leaves its + active duration. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifySlideAnimationsEnd(); + + /** Notify that for the given node, audio output has stopped. + + This method is to be used from the AnimationNode + objects to signal that audio playback has just + stopped. This will invoke all registered audio + stopped handlers. + + @param rNode + Node for which audio has stopped. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifyAudioStopped( const AnimationNodeSharedPtr& rNode ); + + /** Notify that the show has entered or exited pause mode + + This method is to be used from the Presentation object + to signal that a slide is entering (bPauseShow=true) + or exiting (bPauseShow=false) pause mode. This will + invoke all registered slide end handlers. + */ + void notifyPauseMode( bool bPauseShow ); + + /** Notify that all audio has to be stopped. + + This method is used by XCommand nodes and all sound + playing nodes should listen for this command and + stop their sounds when it's fired. + + @return true, if this event was processed by + anybody. If false is returned, no handler processed + this event (and probably, nothing will happen at all) + */ + bool notifyCommandStopAudio( const AnimationNodeSharedPtr& rNode ); + + /** Notifies that a hyperlink has been clicked. + */ + void notifyHyperlinkClicked( OUString const& hyperLink ); + +private: + std::unique_ptr<EventMultiplexerImpl> mpImpl; +}; + +} // namespace Presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EVENTMULTIPLEXER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/eventqueue.hxx b/slideshow/source/inc/eventqueue.hxx new file mode 100644 index 000000000..fb0f1b853 --- /dev/null +++ b/slideshow/source/inc/eventqueue.hxx @@ -0,0 +1,148 @@ +/* -*- 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_INC_EVENTQUEUE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EVENTQUEUE_HXX + +#include <canvas/elapsedtime.hxx> + +#include "event.hxx" + +#include <mutex> +#include <queue> +#include <vector> + + +/* Definition of ActivitiesQueue class */ + +namespace slideshow::internal + { + /** This class handles events in a presentation. Events are + time instants where e.g. effects start. + */ + class EventQueue + { + public: + EventQueue( + std::shared_ptr< ::canvas::tools::ElapsedTime > + const & pPresTimer ); + + ~EventQueue(); + + EventQueue(const EventQueue&) = delete; + EventQueue& operator=(const EventQueue&) = delete; + + /** Add the given event to the queue. The event is fired + at, or shortly after, its Event::getActivationTime instant. + */ + bool addEvent( const EventSharedPtr& event ); + + /** Add the given event to the queue. The event is fired + at, or shortly after, its Event::getActivationTime instant. + The difference to addEvent() is that events added during + process() are postponed to next process(). + */ + bool addEventForNextRound( const EventSharedPtr& event ); + + /** Another way to control the order of asynchronous event + execution. Use this method to schedule events that are to + be executed after all regular events that have no delay, + even when they schedule new regular events without delay. + */ + bool addEventWhenQueueIsEmpty (const EventSharedPtr& rpEvent); + + /** Process the event queue. + + This method executes all events whose timeout has + expired when calling this method (i.e. all events + whose scheduled time is less or equal the current + time). + + Check for the next available event's timeout via + nextTimeout(), or whether the queue is empty + altogether via isEmpty(). + */ + void process(); + + /** Query state of the queue + + @return false, if queue is empty, true otherwise + */ + bool isEmpty() const; + + /** Query timeout for the topmost event in the queue. + + @return Timeout in seconds, until the next event is + ready. The time returned here is relative to the pres + timer (i.e. the timer specified at the EventQueue + constructor). When the queue is empty (i.e. isEmpty() + returns true), the returned value is the highest + representable double value + (::std::numeric_limits<double>::max()). If the topmost + event in the queue is already pending, the timeout + returned here will actually be negative. + */ + double nextTimeout() const; + + /** Remove all pending events from the queue. + */ + void clear(); + + /** Forces an empty queue, firing all events immediately + without minding any times. + @attention do only call from event loop, this calls process_()! + */ + void forceEmpty(); + + /** Gets the queue's timer object. + */ + std::shared_ptr< ::canvas::tools::ElapsedTime > const & + getTimer() const { return mpTimer; } + + private: + mutable std::mutex maMutex; + + struct EventEntry + { + EventSharedPtr pEvent; + double nTime; + + bool operator<( const EventEntry& ) const; // to leverage priority_queue's default compare + + EventEntry( EventSharedPtr const& p, double t ) + : pEvent(p), nTime(t) {} + }; + + typedef ::std::priority_queue< EventEntry > ImplQueueType; + ImplQueueType maEvents; + typedef ::std::vector<EventEntry> EventEntryVector; + EventEntryVector maNextEvents; + ImplQueueType maNextNextEvents; + void process_( bool bFireAllEvents ); + + // perform timing of events via relative time + // measurements. The world time starts, when the + // EventQueue object is created + std::shared_ptr< ::canvas::tools::ElapsedTime > mpTimer; + }; + +} +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EVENTQUEUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/expressionnode.hxx b/slideshow/source/inc/expressionnode.hxx new file mode 100644 index 000000000..6f9139012 --- /dev/null +++ b/slideshow/source/inc/expressionnode.hxx @@ -0,0 +1,78 @@ +/* -*- 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_INC_EXPRESSIONNODE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EXPRESSIONNODE_HXX + +/* Definition of ExpressionNode interface */ + +namespace slideshow::internal + { + /** Interface describing an abstract animation function. + + Use this interface to model time-dependent animation + functions of one variable. + */ + class AnimationFunction + { + public: + virtual ~AnimationFunction() {} + + /** Operator to calculate function value. + + This method calculates the function value for the + given time instant t. + + @param t + Current time instant, must be in the range [0,1] + + @return the function value, typically in relative + user coordinate space ([0,1] range). + */ + virtual double operator()( double t ) const = 0; + + }; + + /** Refinement of AnimationFunction + + This interface is used by the SMIL function parser, to + collapse constant values into precalculated, single nodes. + */ + class ExpressionNode : public AnimationFunction + { + public: + /** Predicate whether this node is constant. + + This predicate returns true, if this node is + neither time- nor ViewInfo dependent. This allows + for certain optimizations, i.e. not the full + expression tree needs be represented by + ExpressionNodes. + + @returns true, if this node is neither time- nor + ViewInfo dependent + */ + virtual bool isConstant() const = 0; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EXPRESSIONNODE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/expressionnodefactory.hxx b/slideshow/source/inc/expressionnodefactory.hxx new file mode 100644 index 000000000..97d047371 --- /dev/null +++ b/slideshow/source/inc/expressionnodefactory.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_EXPRESSIONNODEFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_EXPRESSIONNODEFACTORY_HXX + +#include "expressionnode.hxx" + +#include <memory> + + +/* Definition of ExpressionNodeFactory class */ + +namespace slideshow::internal + { + /** ExpressionNode factory + + This class can be used to generate a wide variety of + ExpressionNode objects, e.g. when parsing SMIL function + expressions. + */ + class ExpressionNodeFactory + { + public: + static std::shared_ptr<ExpressionNode> createConstantValueExpression( double rConstantValue ); + + static std::shared_ptr<ExpressionNode> createValueTExpression (); + + static std::shared_ptr<ExpressionNode> createPlusExpression ( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ); + static std::shared_ptr<ExpressionNode> createMinusExpression ( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ); + static std::shared_ptr<ExpressionNode> createMultipliesExpression( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ); + static std::shared_ptr<ExpressionNode> createDividesExpression ( const std::shared_ptr<ExpressionNode>& rLHS, + const std::shared_ptr<ExpressionNode>& rRHS ); + + /** Composes two ExpressionNode function. + + The resulting expression will calculate + rOuterFunction( rInnerFunction(t) ). + */ + static std::shared_ptr<ExpressionNode> createMinExpression ( const std::shared_ptr<ExpressionNode>& rOuterFunction, + const std::shared_ptr<ExpressionNode>& rInnerFunction ); + + static std::shared_ptr<ExpressionNode> createMaxExpression ( const std::shared_ptr<ExpressionNode>& rOuterFunction, + const std::shared_ptr<ExpressionNode>& rInnerFunction ); + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_EXPRESSIONNODEFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/framerate.hxx b/slideshow/source/inc/framerate.hxx new file mode 100644 index 000000000..0d7e98126 --- /dev/null +++ b/slideshow/source/inc/framerate.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_INC_FRAMERATE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_FRAMERATE_HXX + +#include <sal/types.h> + +namespace slideshow::internal +{ +/** Some frame rate related data. +*/ +class FrameRate +{ +public: + /** The minimum number of frames per second is used to calculate the + minimum number of frames that is to be shown for active activities. + */ + static const sal_Int32 MinimumFramesPerSecond = 10; + + /** Aim high with the number of preferred number of frames per second. + This number is the maximum as well and the true number will be lower. + */ + static const sal_Int32 PreferredFramesPerSecond = 50; +}; + +} // end of namespace slideshow::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/hslcolor.hxx b/slideshow/source/inc/hslcolor.hxx new file mode 100644 index 000000000..b4fbb6109 --- /dev/null +++ b/slideshow/source/inc/hslcolor.hxx @@ -0,0 +1,95 @@ +/* -*- 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_INC_HSLCOLOR_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_HSLCOLOR_HXX + + +/* Definition of HSLColor class */ + +namespace slideshow::internal + { + class RGBColor; + + /** HSL color space class. + */ + class HSLColor + { + public: + HSLColor(); + HSLColor( double nHue, double nSaturation, double nLuminance ); + explicit HSLColor( const RGBColor& rColor ); + + /** Hue of the color. + + @return hue, is in the range [0,360] + */ + double getHue() const { return maHSLTriple.mnHue; } + + /** Saturation of the color. + + @return saturation, is in the range [0,1] + */ + double getSaturation() const { return maHSLTriple.mnSaturation; } + + /** Luminance of the color. + + @return luminance, is in the range [0,1] + */ + double getLuminance() const { return maHSLTriple.mnLuminance; } + + struct HSLTriple + { + HSLTriple(); + HSLTriple( double nHue, double nSaturation, double nLuminance ); + + double mnHue; + double mnSaturation; + double mnLuminance; + }; + + private: + // default copy/assignment are okay + // HSLColor(const HSLColor&); + // HSLColor& operator=( const HSLColor& ); + + HSLTriple maHSLTriple; + + }; + + bool operator==( const HSLColor& rLHS, const HSLColor& rRHS ); + bool operator!=( const HSLColor& rLHS, const HSLColor& rRHS ); + HSLColor operator+( const HSLColor& rLHS, const HSLColor& rRHS ); + HSLColor operator*( double nFactor, const HSLColor& rRHS ); + + /** HSL color linear interpolator. + + @param t + As usual, t must be in the [0,1] range + + @param bCCW + When true, hue interpolation happens counter-clockwise + */ + HSLColor interpolate( const HSLColor& rFrom, const HSLColor& rTo, double t, bool bCCW ); + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_HSLCOLOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/hslcoloranimation.hxx b/slideshow/source/inc/hslcoloranimation.hxx new file mode 100644 index 000000000..3d2bb34c9 --- /dev/null +++ b/slideshow/source/inc/hslcoloranimation.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_INC_HSLCOLORANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_HSLCOLORANIMATION_HXX + +#include "animation.hxx" +#include "hslcolor.hxx" + + +/* Definition of HSLColorAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a HSL color animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a HSL color value. + */ + class HSLColorAnimation : public Animation + { + public: + typedef HSLColor ValueType; + + /** Set the animation to the given color value + + @param rColor + Current animation value. + */ + virtual bool operator()( const HSLColor& rColor ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual HSLColor getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< HSLColorAnimation > HSLColorAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_HSLCOLORANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/hyperlinkarea.hxx b/slideshow/source/inc/hyperlinkarea.hxx new file mode 100644 index 000000000..d7d760c08 --- /dev/null +++ b/slideshow/source/inc/hyperlinkarea.hxx @@ -0,0 +1,98 @@ +/* -*- 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_INC_HYPERLINKAREA_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_HYPERLINKAREA_HXX + +#include <rtl/ustring.hxx> + +#include <memory> +#include <vector> +#include <utility> + +namespace basegfx { + class B2DRange; +} + +/* Definition of HyperlinkArea interface */ + +namespace slideshow::internal + { + /** HyperlinkArea interface + + Implementers of this interface provide information for + hyperlink sensitive areas. + */ + class HyperlinkArea + { + public: + typedef std::pair< ::basegfx::B2DRange, + OUString > HyperlinkRegion; + + typedef std::vector<HyperlinkRegion> HyperlinkRegions; + + /** Request hyperlink-sensitive areas. + + @return a vector of hyperlink-sensitive areas, plus + the URI associated to them. + */ + virtual HyperlinkRegions getHyperlinkRegions() const = 0; + + /** Retrieve priority of link area + + @return the priority of the link area. Link areas with + higher priority will receive hyperlink clicks in favor + of areas with less priority, if they cover the same + place on screen. + */ + virtual double getHyperlinkPriority() const = 0; + + /** Functor struct, for area ordering + + This defines a strict weak ordering of areas, sort key + is the object ptr value. Most typical use is for + associative containers holding areas. + */ + struct lessThanArea + { + // make functor adaptable (to boost::bind) + typedef bool result_type; + + bool operator()(const std::shared_ptr< HyperlinkArea >& rLHS, + const std::shared_ptr< HyperlinkArea >& rRHS) const + { + const double nPrioL( rLHS->getHyperlinkPriority() ); + const double nPrioR( rRHS->getHyperlinkPriority() ); + + // if prios are equal, tie-break on ptr value + return nPrioL == nPrioR ? rLHS.get() < rRHS.get() : nPrioL < nPrioR; + } + }; + + protected: + ~HyperlinkArea() {} + }; + + typedef std::shared_ptr< HyperlinkArea > HyperlinkAreaSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_HYPERLINKAREA_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/iexternalmediashapebase.hxx b/slideshow/source/inc/iexternalmediashapebase.hxx new file mode 100644 index 000000000..1a70ae093 --- /dev/null +++ b/slideshow/source/inc/iexternalmediashapebase.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_INC_IEXTERNALMEDIASHAPEBASE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_IEXTERNALMEDIASHAPEBASE_HXX + +#include <memory> + +#include "shape.hxx" + + +namespace slideshow::internal + { + /** Represents a shape containing playable content rendered by + external engine (e.g. media or applet). + + This interface adds media handling methods to a shape. It + allows starting/stopping and pausing playback. + */ + class IExternalMediaShapeBase : public Shape + { + public: + // Animation methods + + + /** Notify the Shape that it should start with playback + + This method enters playback mode on all registered + views. It makes the media initially visible (for videos). + */ + virtual void play() = 0; + + /** Notify the Shape that it should stop playback + + This method leaves playback mode on all registered + views. The media is then rewound to the start, and + removed from screen (for videos) + */ + virtual void stop() = 0; + + /** Notify the Shape that it should pause playback + + This method stops playback on all registered + views. The media stays visible (for videos) + */ + virtual void pause() = 0; + + /** Query whether the media is currently playing. + */ + virtual bool isPlaying() const = 0; + + /** Set media time in seconds. + + @param fTime + Time in seconds of the media time line, that should now be + presented + */ + virtual void setMediaTime(double fTime) = 0; + + virtual void setLooping(bool /*bLooping*/){}; + }; + + typedef ::std::shared_ptr< IExternalMediaShapeBase > IExternalMediaShapeBaseSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_IEXTERNALMEDIASHAPEBASE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/interruptabledelayevent.hxx b/slideshow/source/inc/interruptabledelayevent.hxx new file mode 100644 index 000000000..a877e206f --- /dev/null +++ b/slideshow/source/inc/interruptabledelayevent.hxx @@ -0,0 +1,140 @@ +/* -*- 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_INC_INTERRUPTABLEDELAYEVENT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_INTERRUPTABLEDELAYEVENT_HXX + +#include "delayevent.hxx" + +namespace slideshow::internal + { + /** Event, which delays calling passed Event's fire() method + the given amount of time. + + This is actually a facade around the passed event object, + that passes on all calls to that object, and the sole + contribution of itself is the delay. + */ + class DelayFacade : public Event + { + public: + DelayFacade( const EventSharedPtr& rEvent, + double nTimeout ) : + Event("DelayFacade"), + mpEvent( rEvent ), + mnTimeout( nTimeout ) + { + } + + virtual bool fire() override + { + if( mpEvent && isCharged() ) + { + // pass on directly - we're supposed to be called + // from EventQueue here, anyway - and if not, + // we're only keeping that incorrect transitively. + return mpEvent->fire(); + } + + return false; + } + + virtual bool isCharged() const override + { + // pass on to wrappee - this ensures that we return + // false on isCharged(), even if the other event has + // been fired outside our own fire() method + return mpEvent && mpEvent->isCharged(); + } + + virtual double getActivationTime( double nCurrentTime ) const override + { + // enforce _our_ timeout to our clients (this + // overrides any timeout possibly set at the wrappee!) + return nCurrentTime + mnTimeout; + } + + virtual void dispose() override + { + mpEvent.reset(); + } + + private: + EventSharedPtr mpEvent; + double mnTimeout; + }; + + /// Return value for makeInterruptableDelay() + struct InterruptableEventPair + { + /** This member contains a pointer to the timeout + event. When enqueued, this event will fire the + requested action only after the specified timeout. + */ + EventSharedPtr mpTimeoutEvent; + + /** This member contains a pointer to the interruption + event. When enqueued, this event will fire + immediately, interrupting a potentially waiting + timeout event. + */ + EventSharedPtr mpImmediateEvent; + }; + + /** Generate an interruptable delay event. + + This function generates a pair of events, that are + especially tailored to achieve the following behaviour: By + default, the given functor is called after the specified + timeout (after insertion of the event into the EventQueue, + of course). But optionally, when the interruption event + InterruptableEventPair::mpImmediateEvent is fired, the + given functor is called <em>at once</em>, and the delay is + ignored (that means, the given functor is guaranteed to be + called at utmost once, and never twice. Furthermore, it is + ensured that both events return false on isCharged(), once + anyone of them has been fired already). + + @param rFunctor + Functor to call when the event fires. + + @param nTimeout + Timeout in seconds, to wait until functor is called. + + @returns a pair of events, where the first one waits the + specified amount of time, and the other fires the given + functor immediately. + */ + template< typename Functor > InterruptableEventPair makeInterruptableDelay( const Functor& rFunctor, + double nTimeout ) + { + InterruptableEventPair aRes; + + aRes.mpImmediateEvent = makeEvent( rFunctor, "makeInterruptableDelay"); + aRes.mpTimeoutEvent = std::make_shared<DelayFacade>( aRes.mpImmediateEvent, + nTimeout ); + + return aRes; + } + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_INTERRUPTABLEDELAYEVENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/intrinsicanimationeventhandler.hxx b/slideshow/source/inc/intrinsicanimationeventhandler.hxx new file mode 100644 index 000000000..97ad6571c --- /dev/null +++ b/slideshow/source/inc/intrinsicanimationeventhandler.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_INC_INTRINSICANIMATIONEVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_INTRINSICANIMATIONEVENTHANDLER_HXX + +#include <memory> + +/* Definition of IntrinsicAnimationEventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling intrinsic animation display modes. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle intrinsic animation events. + */ + class IntrinsicAnimationEventHandler + { + public: + virtual ~IntrinsicAnimationEventHandler() {} + + virtual bool enableAnimations() = 0; + virtual bool disableAnimations() = 0; + }; + + typedef ::std::shared_ptr< IntrinsicAnimationEventHandler > IntrinsicAnimationEventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_INTRINSICANIMATIONEVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/listenercontainer.hxx b/slideshow/source/inc/listenercontainer.hxx new file mode 100644 index 000000000..8deb1be90 --- /dev/null +++ b/slideshow/source/inc/listenercontainer.hxx @@ -0,0 +1,418 @@ +/* -*- 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_INC_LISTENERCONTAINER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_LISTENERCONTAINER_HXX + +#include <algorithm> +#include <memory> +#include <vector> +#include <iterator> + +namespace slideshow::internal { + +struct EmptyBase +{ + struct EmptyGuard{ explicit EmptyGuard(EmptyBase) {} }; + struct EmptyClearableGuard + { + explicit EmptyClearableGuard(EmptyBase) {} + static void clear() {} + }; + + typedef EmptyGuard Guard; + typedef EmptyClearableGuard ClearableGuard; +}; + +template< typename result_type, typename ListenerTargetT > struct FunctionApply +{ + template<typename FuncT> static bool apply( + FuncT func, + ListenerTargetT const& rArg ) + { + return func(rArg); + } +}; + +template<typename ListenerTargetT> struct FunctionApply<void,ListenerTargetT> +{ + template<typename FuncT> static bool apply( + FuncT func, + ListenerTargetT const& rArg ) + { + func(rArg); + return true; + } +}; + +template< typename ListenerT > struct ListenerOperations +{ + /// Notify a single one of the listeners + template< typename ContainerT, + typename FuncT > + static bool notifySingleListener( ContainerT& rContainer, + FuncT func ) + { + // true: a handler in this queue processed the event + // false: no handler in this queue finally processed the event + return std::any_of( rContainer.begin(), + rContainer.end(), + func ); + } + + /// Notify all listeners + template< typename ContainerT, + typename FuncT > + static bool notifyAllListeners( ContainerT& rContainer, + FuncT func ) + { + bool bRet(false); + for( const auto& rCurr : rContainer ) + { + if( FunctionApply< typename ::std::invoke_result< FuncT, const typename ContainerT::value_type& >::type, + typename ContainerT::value_type >::apply( + func, + rCurr) ) + { + bRet = true; + } + } + + // true: at least one handler returned true + // false: not a single handler returned true + return bRet; + } + + /// Prune container from deceased listeners + template< typename ContainerT > + static void pruneListeners( ContainerT&, size_t ) + { + } +}; + +template< typename ListenerTargetT > +struct ListenerOperations< std::weak_ptr<ListenerTargetT> > +{ + template< typename ContainerT, + typename FuncT > + static bool notifySingleListener( ContainerT& rContainer, + FuncT func ) + { + for( const auto& rCurr : rContainer ) + { + std::shared_ptr<ListenerTargetT> pListener( rCurr.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<ListenerTargetT> pListener( rCurr.lock() ); + + if( pListener.get() && + FunctionApply<typename ::std::invoke_result<FuncT, std::shared_ptr<ListenerTargetT> const&>::type, + std::shared_ptr<ListenerTargetT> >::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.expired() ) + aAliveListeners.push_back( rCurr ); + } + + std::swap( rContainer, aAliveListeners ); + } +}; + +/** Container for objects that can be notified. + + This templatized container holds listener objects, then can get + notified (by calling certain methods on them). + + @tpl Listener + Type for the listener objects to be held + + @tpl ContainerT + Full type of the container to store the listener objects. Defaults + to std::vector<ListenerT> + + @tpl MaxDeceasedListenerUllage + Threshold, from which upwards the listener container gets + pruned. Avoids frequent copying of nearly empty containers. + + @attention internal class, not to be used. functionality is + incomplete, please use the Thread(Un)safeListenerContainer types + from below +*/ +template< typename ListenerT, + typename MutexHolderBaseT, + typename ContainerT=std::vector<ListenerT>, + size_t MaxDeceasedListenerUllage=16 > class ListenerContainerBase : public MutexHolderBaseT +{ + typedef typename MutexHolderBaseT::Guard Guard; + typedef typename MutexHolderBaseT::ClearableGuard ClearableGuard; + +public: + typedef ListenerT listener_type; + typedef ContainerT container_type; + + /** Check whether listener container is empty + + @return true, if currently no listeners registered. Note that + in a multi-threaded scenario, without external synchronisation + to this object, the return value might become wrong at any time. + */ + bool isEmpty() const + { + Guard aGuard(*this); + return maListeners.empty(); + } + + /** Check whether given listener is already added + + @return true, if given listener is already added. + */ + bool isAdded( listener_type const& rListener ) const + { + Guard aGuard(*this); + + const typename container_type::const_iterator aEnd( maListeners.end() ); + if( std::find( maListeners.begin(), + aEnd, + rListener ) != aEnd ) + { + return true; // already added + } + + return false; + } + + /** Add new listener + + @param rListener + Listener to add + */ + void add( listener_type const& rListener ) + { + Guard aGuard(*this); + + // ensure uniqueness + if( isAdded(rListener) ) + return; // already added + + maListeners.push_back( rListener ); + + ListenerOperations<ListenerT>::pruneListeners( + maListeners, + MaxDeceasedListenerUllage); + } + + /** Add new listener into sorted container + + The stored listeners are kept sorted (using this method + requires listener_type to have operator< defined on it). Make + sure to call addSorted() for <em>each</em> listener to add to + this container - sorting is performed under the assumption + that existing entries are already sorted. + + @param rListener + Listener to add + + @return false, if the listener is already added, true + otherwise + */ + bool addSorted( listener_type const& rListener ) + { + Guard aGuard(*this); + + // ensure uniqueness + if( isAdded(rListener) ) + return false; // already added + + maListeners.push_back( rListener ); + + // a single entry does not need to be sorted + if( maListeners.size() > 1 ) + { + std::inplace_merge( + maListeners.begin(), + std::prev(maListeners.end()), + maListeners.end() ); + } + + ListenerOperations<ListenerT>::pruneListeners( + maListeners, + MaxDeceasedListenerUllage); + + return true; + } + + /** Remove listener from container + + @param rListener + The listener to remove + + @return false, if listener not found in container, true + otherwise + */ + bool remove( listener_type const& rListener ) + { + Guard aGuard(*this); + + const typename container_type::iterator aEnd( maListeners.end() ); + typename container_type::iterator aIter; + if( (aIter=std::remove(maListeners.begin(), + aEnd, + rListener)) == aEnd ) + { + return false; // listener not found + } + + maListeners.erase( aIter, aEnd ); + + return true; + } + + /// Removes all listeners in one go + void clear() + { + Guard aGuard(*this); + + maListeners.clear(); + } + + /** Apply functor to one listener + + This method applies functor to one of the listeners. Starting + with the first entry of the container, the functor is called + with the listener entries, until it returns true. + + @param func + Given functor is called with listeners, until it returns true + + @return true, if functor was successfully applied to a + listener + */ + template< typename FuncT > bool apply( FuncT func ) const + { + ClearableGuard aGuard(*this); + + // generate a local copy of all handlers, to make method + // reentrant and thread-safe. + container_type const local( maListeners ); + aGuard.clear(); + + const bool bRet( + ListenerOperations<ListenerT>::notifySingleListener( + local, + func )); + + { + Guard aGuard2(*this); + ListenerOperations<ListenerT>::pruneListeners( + const_cast<container_type&>(maListeners), + MaxDeceasedListenerUllage); + } + + return bRet; + } + + /** Apply functor to all listeners + + This method applies functor to all of the listeners. Starting + with the first entry of the container, the functor is called + with the listener entries. + + @param func + Given functor is called with listeners. + + @return true, if functor was successfully applied to at least + one listener + */ + template< typename FuncT > bool applyAll( FuncT func ) const + { + ClearableGuard aGuard(*this); + + // generate a local copy of all handlers, to make method + // reentrant and thread-safe. + container_type const local( maListeners ); + aGuard.clear(); + + const bool bRet( + ListenerOperations<ListenerT>::notifyAllListeners( + local, + func )); + + { + Guard aGuard2(*this); + ListenerOperations<ListenerT>::pruneListeners( + const_cast<container_type&>(maListeners), + MaxDeceasedListenerUllage); + } + + return bRet; + } + +private: + ContainerT maListeners; +}; + + +/** ListenerContainer variant that does not serialize access + + This ListenerContainer version is not safe to use in a + multi-threaded scenario, but has less overhead. + */ +template< typename ListenerT, + typename ContainerT=std::vector<ListenerT> > +class ThreadUnsafeListenerContainer : public ListenerContainerBase<ListenerT, + EmptyBase, + ContainerT> +{ +}; + +} // namespace slideshow::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_LISTENERCONTAINER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/mediafilemanager.hxx b/slideshow/source/inc/mediafilemanager.hxx new file mode 100644 index 000000000..a0ec69556 --- /dev/null +++ b/slideshow/source/inc/mediafilemanager.hxx @@ -0,0 +1,34 @@ +/* -*- 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_INC_MEDIAFILEMANAGER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_MEDIAFILEMANAGER_HXX + +#include <rtl/ustring.hxx> + +#include <memory> + +namespace avmedia +{ +struct MediaTempFile; +} + +namespace slideshow::internal +{ +class MediaFileManager +{ +public: + virtual ~MediaFileManager(){}; + virtual std::shared_ptr<avmedia::MediaTempFile> getMediaTempFile(const OUString& aUrl) = 0; +}; +} +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_MEDIAFILEMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/mouseeventhandler.hxx b/slideshow/source/inc/mouseeventhandler.hxx new file mode 100644 index 000000000..6be068e8a --- /dev/null +++ b/slideshow/source/inc/mouseeventhandler.hxx @@ -0,0 +1,113 @@ +/* -*- 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_INC_MOUSEEVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_MOUSEEVENTHANDLER_HXX + +#include <sal/types.h> + +#include <memory> + +namespace com::sun::star::awt { struct MouseEvent; } + + +/* Definition of MouseEventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling mouse events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle mouse events. + */ + class MouseEventHandler + { + public: + virtual ~MouseEventHandler() {} + + /** Handle a mouse button pressed event. + + @param e + The mouse event that occurred. The x,y coordinates of + the event are already transformed back to user + coordinate space, taking the inverse transform of the + view in which the event occurred. + + @return true, if this handler has successfully + processed the mouse event. When this method returns + false, possibly other, less prioritized handlers can be + called, too. + */ + virtual bool handleMousePressed( const css::awt::MouseEvent& e ) = 0; + + /** Handle a mouse button released event. + + @param e + The mouse event that occurred. The x,y coordinates of + the event are already transformed back to user + coordinate space, taking the inverse transform of the + view in which the event occurred. + + @return true, if this handler has successfully + processed the pause event. When this method returns + false, possibly other, less prioritized handlers are + called, too. + */ + virtual bool handleMouseReleased( const css::awt::MouseEvent& e ) = 0; + + /** Handle a mouse was moved with a pressed button event. + + @param e + The mouse event that occurred. The x,y coordinates of + the event are already transformed back to user + coordinate space, taking the inverse transform of the + view in which the event occurred. + + @return true, if this handler has successfully + processed the pause event. When this method returns + false, possibly other, less prioritized handlers are + called, too. + */ + virtual bool handleMouseDragged( const css::awt::MouseEvent& e ) = 0; + + /** Handle a mouse was moved event. + + @param e + The mouse event that occurred. The x,y coordinates of + the event are already transformed back to user + coordinate space, taking the inverse transform of the + view in which the event occurred. + + @return true, if this handler has successfully + processed the pause event. When this method returns + false, possibly other, less prioritized handlers are + called, too. + */ + virtual bool handleMouseMoved( const css::awt::MouseEvent& e ) = 0; + }; + + typedef ::std::shared_ptr< MouseEventHandler > MouseEventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_MOUSEEVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/numberanimation.hxx b/slideshow/source/inc/numberanimation.hxx new file mode 100644 index 000000000..e35769e97 --- /dev/null +++ b/slideshow/source/inc/numberanimation.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_NUMBERANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_NUMBERANIMATION_HXX + +#include "animation.hxx" + + +/* Definition of NumberAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a number animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a single floating point value. + */ + class NumberAnimation : public Animation + { + public: + typedef double ValueType; + + /** Set the animation to value x + + @param x + Current animation value (must be in an + attribute-specific permissible range). Overflowing + values will be clipped to the permissible range + internally. + */ + virtual bool operator()( double x ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual double getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< NumberAnimation > NumberAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_NUMBERANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/pairanimation.hxx b/slideshow/source/inc/pairanimation.hxx new file mode 100644 index 000000000..45b90ebbb --- /dev/null +++ b/slideshow/source/inc/pairanimation.hxx @@ -0,0 +1,69 @@ +/* -*- 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_INC_PAIRANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_PAIRANIMATION_HXX + +#include "animation.hxx" +#include <basegfx/tuple/b2dtuple.hxx> + + +/* Definition of PairAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a pair of numbers animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a pair of floating point values (e.g. a position or a + size). + */ + class PairAnimation : public Animation + { + public: + typedef ::basegfx::B2DTuple ValueType; + + /** Set the animation to the given value + + @param rValue + Current animation value. + */ + virtual bool operator()( const ::basegfx::B2DTuple& rValue ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual ::basegfx::B2DTuple getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< PairAnimation > PairAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_PAIRANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/pauseeventhandler.hxx b/slideshow/source/inc/pauseeventhandler.hxx new file mode 100644 index 000000000..3d61d6ee1 --- /dev/null +++ b/slideshow/source/inc/pauseeventhandler.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_INC_PAUSEEVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_PAUSEEVENTHANDLER_HXX + +#include <memory> + + +/* Definition of PauseHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling pause events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle pause events. + */ + class PauseEventHandler + { + public: + /** Handle the event. + + @param bPauseShow + When true, the show is paused. When false, the show is + started again + + @return true, if this handler has successfully + processed the pause event. When this method returns + false, possibly other, less prioritized handlers are + called, too. + */ + virtual bool handlePause( bool bPauseShow ) = 0; + + protected: + ~PauseEventHandler() {} + }; + + typedef ::std::shared_ptr< PauseEventHandler > PauseEventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_PAUSEEVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/rgbcolor.hxx b/slideshow/source/inc/rgbcolor.hxx new file mode 100644 index 000000000..b5f3f8e61 --- /dev/null +++ b/slideshow/source/inc/rgbcolor.hxx @@ -0,0 +1,93 @@ +/* -*- 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_INC_RGBCOLOR_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_RGBCOLOR_HXX + +#include <cppcanvas/color.hxx> + + +/* Definition of RGBColor class */ + +namespace slideshow::internal + { + class HSLColor; + + /** RGB color space class. + */ + class RGBColor + { + public: + RGBColor(); + explicit RGBColor( ::cppcanvas::IntSRGBA nRGBColor ); + RGBColor( double nRed, double nGreen, double nBlue ); + explicit RGBColor( const HSLColor& rColor ); + + /** Get the RGB red value. + */ + double getRed() const { return maRGBTriple.mnRed; } + + /** Get the RGB green value. + */ + double getGreen() const { return maRGBTriple.mnGreen; } + + /** Get the RGB blue value. + */ + double getBlue() const { return maRGBTriple.mnBlue; } + + /** Create an integer sRGBA color. + */ + ::cppcanvas::IntSRGBA getIntegerColor() const; + + struct RGBTriple + { + RGBTriple(); + RGBTriple( double nRed, double nGreen, double nBlue ); + + double mnRed; + double mnGreen; + double mnBlue; + }; + + private: + // default copy/assignment are okay + // RGBColor(const RGBColor&); + // RGBColor& operator=( const RGBColor& ); + + RGBTriple maRGBTriple; + }; + + bool operator==( const RGBColor& rLHS, const RGBColor& rRHS ); + bool operator!=( const RGBColor& rLHS, const RGBColor& rRHS ); + RGBColor operator+( const RGBColor& rLHS, const RGBColor& rRHS ); + RGBColor operator*( const RGBColor& rLHS, const RGBColor& rRHS ); + RGBColor operator*( double nFactor, const RGBColor& rRHS ); + + + /** RGB color linear interpolator. + + @param t + As usual, t must be in the [0,1] range + */ + RGBColor interpolate( const RGBColor& rFrom, const RGBColor& rTo, double t ); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_RGBCOLOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/screenupdater.hxx b/slideshow/source/inc/screenupdater.hxx new file mode 100644 index 000000000..4ae477519 --- /dev/null +++ b/slideshow/source/inc/screenupdater.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_INC_SCREENUPDATER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SCREENUPDATER_HXX + +#include "viewupdate.hxx" +#include "unoviewcontainer.hxx" +#include <memory> + +/* Definition of ScreenUpdater class */ + +namespace slideshow::internal + { + /** Screen updater + + This class handles and synchronizes screen updates + centrally. Therefore, it can hold a number of ViewUpdate + objects, which are polled for pending updates on + commitUpdates(). Furthermore, external code can request + updates via notifyUpdate() calls. If neither such an + update was requested, nor any of the registered ViewUpdate + objects report any pending update, commitUpdates() does + nothing. + */ + class ScreenUpdater + { + public: + explicit ScreenUpdater( UnoViewContainer const& rViewContainer ); + ~ScreenUpdater(); + ScreenUpdater(const ScreenUpdater&) = delete; + ScreenUpdater& operator=(const ScreenUpdater&) = delete; + + /** Notify screen update + + This method records a screen content update request + for all views. + */ + void notifyUpdate(); + + /** Notify screen update + + This method records a screen content update request + for the given view. + + @param rView + The view that needs an update + + @param bViewClobbered + When true, notifies update that view content is + clobbered by external circumstances (e.g. by another + application), and needs update even if the + implementation 'thinks' it does not need to render + something to screen. + */ + void notifyUpdate( const UnoViewSharedPtr& rView, bool bViewClobbered ); + + /** Commits collected update actions + */ + void commitUpdates(); + + /** Register ViewUpdate + + @param rViewUpdate + Add this ViewUpdate to the list that's asked for + pending updates + */ + void addViewUpdate( ViewUpdateSharedPtr const& rViewUpdate ); + + /** Unregister ViewUpdate + + @param rViewUpdate + Remove this ViewUpdate from the list that's asked for + pending updates + */ + void removeViewUpdate( ViewUpdateSharedPtr const& ); + + /** A wart. + + Used to fire an immediate screen update. Currently + needed for the wait symbol, since switching that on + and off does get to commitUpdates() + */ + void requestImmediateUpdate(); + + class UpdateLock { + public: + virtual void Activate() = 0; + + protected: + ~UpdateLock() {} + }; + + /** Call this method to create a lock instead of calling + lockUpdates() and unlockUpdates() directly. + */ + ::std::shared_ptr<UpdateLock> createLock(); + + /** Lock updates to prevent intermediate repaints. + */ + void lockUpdates(); + + /** When called as often as lockUpdates() then commitUpdates() + is called. + */ + void unlockUpdates(); + + private: + struct ImplScreenUpdater; + std::unique_ptr<ImplScreenUpdater> mpImpl; + + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SCREENUPDATER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shape.hxx b/slideshow/source/inc/shape.hxx new file mode 100644 index 000000000..bbd2988e0 --- /dev/null +++ b/slideshow/source/inc/shape.hxx @@ -0,0 +1,283 @@ +/* -*- 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_INC_SHAPE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPE_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/drawing/XShape.hpp> + +#include <basegfx/range/b2drectangle.hxx> + +#include "viewlayer.hxx" + +#include <memory> +#include <set> + +namespace basegfx { + class B2DRange; +} + +namespace slideshow::internal + { + // forward declaration necessary, because methods use ShapeSharedPtr + class Shape; + + typedef ::std::shared_ptr< Shape > ShapeSharedPtr; + + /** Represents a slide's shape object. + + This interface represents the view-independent aspects of a + slide's shape, providing bound rect, underlying XShape and + basic paint methods. + */ + class Shape + { + public: + Shape() : mbIsForeground(true) {} + virtual ~Shape() {} + Shape(const Shape&) = delete; + Shape& operator=(const Shape&) = delete; + + /** Get the associated XShape of this shape. + + @return the associated XShape. If this method returns + an empty reference, this object might be one of the + special-purpose shapes of a slide, which have no + direct corresponding XShape (the background comes to + mind here). + */ + virtual css::uno::Reference< css::drawing::XShape > getXShape() const = 0; + + + // View layer methods + + + /** Add a new view layer. + + This method adds a new view layer, this shape shall + show itself on. + + @param rNewLayer + New layer to show on + + @param bRedrawLayer + Redraw shape on given layer + */ + virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer, + bool bRedrawLayer ) = 0; + + /** Withdraw the shape from a view layer + + This method removes the shape from the given view + layer. + + @return true, if the shape was successfully removed + */ + virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) = 0; + + /** Withdraw all view layers at once + + This method will be faster than repeated + removeViewLayer() calls. + */ + virtual void clearAllViewLayers() = 0; + + // render methods + + + /** Update the shape + + This method updates the Shape on all registered view + layers, but only if shape content has actually + changed. + + @return whether the update finished successfully. + */ + virtual bool update() const = 0; + + /** Render the shape. + + This method renders the shape on all registered view + layers, regardless of whether shape content has + changed or not. + + @return whether the rendering finished successfully. + */ + virtual bool render() const = 0; + + /** Query whether shape content changed + + This method returns true, if shape content changed + since the last rendering (i.e. the shape needs an + update to reflect that changed content on the views). + */ + virtual bool isContentChanged() const = 0; + + + // Shape attributes + + + /** Get the current shape position and size. + + This method yields the currently effective shape + bounds (which might change over time, for animated + shapes). Please note that possibly shape rotations + from its original document state must not be taken + into account here: if you need the screen bounding + box, use getUpdateArea() instead. Note further that + shape rotations, which are already contained in the + shape as displayed in the original document + <em>are</em> included herein (we currently take the + shape as-is from the document, assuming a rotation + angle of 0). + */ + virtual ::basegfx::B2DRectangle getBounds() const = 0; + + /** Get the DOM position and size of the shape. + + This method yields the underlying DOM shape bounds, + i.e. the original shape bounds from the document + model. This value is <em>always</em> unaffected by any + animation activity. Note that shape rotations, which + are already contained in the shape as displayed in the + original document are already included herein (we + currently take the shape as-is from the document, + assuming a rotation angle of 0). + */ + virtual ::basegfx::B2DRectangle getDomBounds() const = 0; + + /** Get the current shape update area. + + This method yields the currently effective update area + for the shape, i.e. the area that needs to be updated, + should the shape be painted. Normally, this will be + the (possibly rotated and sheared) area returned by + getBounds(). + */ + virtual ::basegfx::B2DRectangle getUpdateArea() const = 0; + + /** Query whether the shape is visible at all. + + @return true, if this shape is visible, false + otherwise. + */ + virtual bool isVisible() const = 0; + + /** Get the shape priority. + + The shape priority defines the relative order of the + shapes on the slide. + + @return the priority. Will be in the [0,+infty) range. + */ + virtual double getPriority() const = 0; + + /** Query whether the Shape is currently detached from the + background. + + This method checks whether the Shape is currently + detached from the slide background, i.e. whether shape + updates affect the underlying slide background or + not. A shape that returns true here must not alter + slide content in any way when called render() or + update() (this is normally achieved by making this + shape a sprite). + */ + virtual bool isBackgroundDetached() const = 0; + + /** Check whether the shape belongs to the foreground + + For instance, if the shape is in the master slide + it does not belong to the foreground. + + @return true if the shape is on the foreground + */ + virtual bool isForeground() const { return mbIsForeground; }; + + /** + Set the flag that holds whether the shape is + in the foreground or not + + @param bIsForeground + Shape is on the foreground + */ + virtual void setIsForeground( const bool bIsForeground ) { mbIsForeground = bIsForeground; }; + + // Misc + + + /** Functor struct, for shape ordering + + This defines a strict weak ordering of shapes, primary + sort key is the shape priority, and secondary sort key + the object ptr value. Most typical use is for + associative containers holding shapes (and which also + have to maintain something like a paint order). + */ + struct lessThanShape + { + // make functor adaptable (to boost::bind) + typedef bool result_type; + + // since the ZOrder property on the XShape has somewhat + // peculiar attributes (it's basically the index of the shapes + // in the drawing layer's SdrObjList - which means, it starts + // from 0 for children of group objects), we cannot use it to determine + // drawing order. Thus, we rely on importer-provided order values here, + // which is basically a running counter during shape import (i.e. denotes + // the order of shape import). This is the correct order, at least for the + // current drawing core. + + // If, someday, the above proposition is no longer true, one directly use + // the shape's ZOrder property + + static bool compare(const Shape* pLHS, const Shape* pRHS) + { + const double nPrioL( pLHS->getPriority() ); + const double nPrioR( pRHS->getPriority() ); + + // if prios are equal, tie-break on ptr value + return nPrioL == nPrioR ? pLHS < pRHS : nPrioL < nPrioR; + } + + bool operator()(const ShapeSharedPtr& rLHS, const ShapeSharedPtr& rRHS) const + { + return compare(rLHS.get(),rRHS.get()); + } + + }; + + private: + /** Flag to check whether the shape belongs to the foreground. + + For instance, it is false if the shape belongs to the master slide. + */ + bool mbIsForeground; + }; + + /** A set which contains all shapes in an ordered fashion. + */ + typedef ::std::set< ShapeSharedPtr, Shape::lessThanShape > ShapeSet; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapeattributelayer.hxx b/slideshow/source/inc/shapeattributelayer.hxx new file mode 100644 index 000000000..b97722fee --- /dev/null +++ b/slideshow/source/inc/shapeattributelayer.hxx @@ -0,0 +1,543 @@ +/* -*- 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_INC_SHAPEATTRIBUTELAYER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEATTRIBUTELAYER_HXX + +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include "rgbcolor.hxx" + +#include <memory> + + +namespace slideshow::internal + { + + /** This interface represents a stateful object. + + The state ID returned by the getStateId() method + abstractly encodes the object's state. When this ID + changes, clients can assume that the object's state has + changed. + */ + class State final + { + public: + + /// Abstract, numerically encoded state ID + typedef ::std::size_t StateId; + }; + + class ShapeAttributeLayer; + + typedef ::std::shared_ptr< ShapeAttributeLayer > ShapeAttributeLayerSharedPtr; + + /** Encapsulates all modifiable attributes of a shape. + + This class holds all modifiable attributes of a shape, and + at the same time provides means to layer attributes on top + of each other... + + And yes, there's a reason why we even pass bools and ints + by const reference. Namely, that makes the set* methods + differ only in the value type, which greatly reduces + template variability (e.g. in AnimationFactory). + */ + class ShapeAttributeLayer + { + public: + /** Create a ShapeAttributeLayer instance, with all + attributes set to default. + + Furthermore, this constructor gets a pointer to a + child layer, which is used as the fallback (or the + base value) for all attributes + + @param rChildLayer + Layer below this one + + @attention + This method is only supposed to be called from Shape objects + */ + explicit ShapeAttributeLayer( const ShapeAttributeLayerSharedPtr& rChildLayer ); + + // Children management methods + + + /** Revoke the given layer. + + This method revokes the given layer from this object + or one of the children. That is, if this object does + have children, and the given layer is no direct child, + it is recursively passed to the children for removal. + + @return true, if removal was successful. + + @attention + This method is only supposed to be called from Shape objects + */ + bool revokeChildLayer( const ShapeAttributeLayerSharedPtr& rChildLayer ); + + /** Query the child layer of this object. + + @attention + This method is only supposed to be called from Shape objects + */ + const ShapeAttributeLayerSharedPtr& getChildLayer() const; + + /** Set the additive mode for possible child attributes + + This method sets the additive mode for child + attributes. That is the way underlying attribute + layers are combined with this one (i.e. to overrule + lower layers, or how to combine the values). The + default is + css::animations::AnimationAdditiveMode::BASE, + which means, take the value of the underlying layers, + or from the model shape itself. + + @param nMode + Must be one of + css::animations::AnimationAdditiveMode. + */ + void setAdditiveMode( sal_Int16 nMode ); + + // Attribute methods + + + /** Query whether the width attribute is valid. + */ + bool isWidthValid() const; + /** Query the current width of the shape + */ + double getWidth() const; + /** Set the new width of the shape + + @param rNewWidth + A negative width mirrors the shape. + */ + void setWidth( const double& rNewWidth ); + + /** Query whether the height attribute is valid. + */ + bool isHeightValid() const; + /** Query the current height of the shape + */ + double getHeight() const; + /** Set the new height of the shape + + @param rNewHeight + A negative height mirrors the shape. + */ + void setHeight( const double& rNewHeight ); + + /** Set the new size of the shape + + @param rNewSize + A negative size mirrors the shape. + */ + void setSize( const ::basegfx::B2DSize& rNewSize ); + + /** Query whether the x position attribute is valid + */ + bool isPosXValid() const; + /** Query the current x position of the shape. + + The current x position of the shape is always relative + to the <em>center</em> of the shape (in contrast to + the Shape::getBounds() and Shape::getUpdateArea() + methods). + */ + double getPosX() const; + /** Set the new x position of the shape + + The current x position of the shape is always relative + to the <em>center</em> of the shape (in contrast to + the Shape::getBounds() and Shape::getUpdateArea() + methods). + */ + void setPosX( const double& rNewX ); + + /** Query whether the y position attribute is valid + */ + bool isPosYValid() const; + /** Query the current y position of the shape + + The current y position of the shape is always relative + to the <em>center</em> of the shape (in contrast to + the Shape::getBounds() and Shape::getUpdateArea() + methods). + */ + double getPosY() const; + /** Set the new y position of the shape + + The current y position of the shape is always relative + to the <em>center</em> of the shape (in contrast to + the Shape::getBounds() and Shape::getUpdateArea() + methods). + */ + void setPosY( const double& rNewY ); + + /** Set the new position of the shape + + The current position of the shape is always relative + to the <em>center</em> of the shape (in contrast to + the Shape::getBounds() and Shape::getUpdateArea() + methods). + */ + void setPosition( const ::basegfx::B2DPoint& rNewPos ); + + /** Query whether the rotation angle attribute is valid + */ + bool isRotationAngleValid() const; + /** Query the current rotation angle of the shape + + @return the rotation angle in degrees. + */ + double getRotationAngle() const; + /** Set the new rotation angle of the shape + + @param rNewAngle + New rotation angle in degrees. + */ + void setRotationAngle( const double& rNewAngle ); + + /** Query whether the shear x angle attribute is valid + */ + bool isShearXAngleValid() const; + /** Query the current shear angle at the x axis of the shape + + @return the shear angle in radians. + */ + double getShearXAngle() const; + /** Set the new shear angle at the x axis of the shape + + @param rNewAngle + New shear angle in radians. + */ + void setShearXAngle( const double& rNewAngle ); + + /** Query whether the shear y angle attribute is valid + */ + bool isShearYAngleValid() const; + /** Query the current shear angle at the y axis of the shape + + @return the shear angle in radians. + */ + double getShearYAngle() const; + /** Set the new shear angle at the y axis of the shape + + @param rNewAngle + New shear angle in radians. + */ + void setShearYAngle( const double& rNewAngle ); + + /** Query whether the alpha attribute is valid + */ + bool isAlphaValid() const; + /** Query the current alpha value of the shape + */ + double getAlpha() const; + /** Set the new alpha value of the shape + + @param rNewValue + New alpha value, must be in the [0,1] range + */ + void setAlpha( const double& rNewValue ); + + /** Query whether the clip attribute is valid + */ + bool isClipValid() const; + /** Query the current clip polygon of the shape + */ + ::basegfx::B2DPolyPolygon getClip() const; + /** Set the new clip polygon of the shape + + @param rNewClip + New clip polygon, is interpreted in shape view coordinates, but + relative to the shape (i.e. the origin of the shape coincides + with the origin of the clip polygon). + */ + void setClip( const ::basegfx::B2DPolyPolygon& rNewClip ); + + /** Query whether the dim color attribute is valid + + The dim color globally 'dims' the shape towards that + color + */ + bool isDimColorValid() const; + /** Get the dim color for the whole shape. + */ + RGBColor getDimColor() const; + /** Set the dim color globally for the whole shape. + */ + void setDimColor( const RGBColor& nNewColor ); + + /** Query whether the fill color attribute is valid + */ + bool isFillColorValid() const; + /** Get the fill color for the whole shape. + + If there's no unique fill color, the color from the + first filled polygon is returned. + */ + RGBColor getFillColor() const; + /** Set the fill color globally for the whole shape. + */ + void setFillColor( const RGBColor& nNewColor ); + + /** Query whether the line color attribute is valid + */ + bool isLineColorValid() const; + /** Get the line color for the whole shape. + + If there's no unique line color, the color from the + first line is returned. + */ + RGBColor getLineColor() const; + /** Set the line color globally for the whole shape. + */ + void setLineColor( const RGBColor& nNewColor ); + + /** Query whether the fill mode attribute is valid + */ + bool isFillStyleValid() const; + /** Get the current fill mode for polygon fillings. + + @returns the current style + */ + sal_Int16 getFillStyle() const; + /** Changes polygon fillings. + */ + void setFillStyle( const sal_Int16& rStyle ); + + /** Query whether the line mode attribute is valid + */ + bool isLineStyleValid() const; + /** Get the current line mode for line drawing. + + @returns the current line style + */ + sal_Int16 getLineStyle() const; + /** Set line style for the whole shape + */ + void setLineStyle( const sal_Int16& rStyle ); + + /** Query whether the visibility state attribute is valid + */ + bool isVisibilityValid() const; + /** Get the current shape visibility. + + @returns true for visible, false for invisible. + */ + bool getVisibility() const; + /** Set the shape visibility + */ + void setVisibility( const bool& bVisible ); + + /** Query whether the char color attribute is valid + */ + bool isCharColorValid() const; + /** Get the text color for the whole shape. + + If there's no unique text color, the color from the + first text drawn is returned. + */ + RGBColor getCharColor() const; + /** Set the text color globally for the whole shape. + */ + void setCharColor( const RGBColor& nNewColor ); + + /** Query whether the char weight attribute is valid + */ + bool isCharWeightValid() const; + /** Get the current char weight value for the whole shape. + + @returns the value for the char weight. The value must + be out of the css::awt::FontWeight + constant group. + */ + double getCharWeight() const; + /** Set the char weight globally for the whole shape. + + The value must be out of the + css::awt::FontWeight constant group. + */ + void setCharWeight( const double& rStyle ); + + /** Query whether the underline mode attribute is valid + */ + bool isUnderlineModeValid() const; + /** Get the current text underline status for the whole shape. + + If there is no unique underline status, false is returned. + + @returns true for underlined text, false for normal. + */ + sal_Int16 getUnderlineMode() const; + /** Set the underline status globally for the whole shape + */ + void setUnderlineMode( const sal_Int16& bUnderline ); + + /** Query whether the font family attribute is valid + */ + bool isFontFamilyValid() const; + /** Get the current text font family for the whole shape. + + If there is no unique font family, the font family of + the first text of the shape is returned. + */ + OUString getFontFamily() const; + /** Set the text font family name globally for the whole shape + */ + void setFontFamily( const OUString& rName ); + + /** Query whether the italic mode attribute is valid + */ + bool isCharPostureValid() const; + /** Get the current text italic style for the whole shape. + + @returns the italic style. The value returned is one + of the css::awt::FontSlant enums + */ + sal_Int16 getCharPosture() const; + /** Set the italic style globally for the whole shape. + + The value must be one of the + css::awt::FontSlant enums. + */ + void setCharPosture( const sal_Int16& rStyle ); + + /** Query whether the char scaling attribute is valid + */ + bool isCharScaleValid() const; + /** Query the current char scaling attribute globally for + the shape. + + The char scaling changes the scale of the whole shape + text (uniformly, i.e. both in x and in y direction). + */ + double getCharScale() const; + /** Set the new char scale globally for the shape + + @param rNewScale + New char scale + */ + void setCharScale( const double& rNewScale ); + + // State change query methods + + + State::StateId getTransformationState() const; + State::StateId getClipState() const; + State::StateId getAlphaState() const; + State::StateId getPositionState() const; + State::StateId getContentState() const; + State::StateId getVisibilityState() const; + + private: + // default copy/assignment operator is okay + // ShapeAttributeLayer(const ShapeAttributeLayer&); + // ShapeAttributeLayer& operator=( const ShapeAttributeLayer& ); + + bool haveChild() const { return static_cast< bool >(mpChild); } + void updateStateIds(); + + template< typename T > T calcValue( const T& rCurrValue, + bool bThisInstanceValid, + bool (ShapeAttributeLayer::*pIsValid)() const, + T (ShapeAttributeLayer::*pGetValue)() const ) const; + + ShapeAttributeLayerSharedPtr mpChild; // may be NULL + + ::basegfx::B2DSize maSize; + ::basegfx::B2DPoint maPosition; + ::basegfx::B2DPolyPolygon maClip; + + OUString maFontFamily; + + double mnRotationAngle; + double mnShearXAngle; + double mnShearYAngle; + double mnAlpha; + double mnCharScale; + double mnCharWeight; + + css::drawing::FillStyle meFillStyle; + css::drawing::LineStyle meLineStyle; + css::awt::FontSlant meCharPosture; + sal_Int16 mnUnderlineMode; + + RGBColor maDimColor; + RGBColor maFillColor; + RGBColor maLineColor; + RGBColor maCharColor; + + State::StateId mnTransformationState; + State::StateId mnClipState; + State::StateId mnAlphaState; + State::StateId mnPositionState; + State::StateId mnContentState; + State::StateId mnVisibilityState; + + sal_Int16 mnAdditiveMode; + + bool mbVisibility : 1; + + bool mbWidthValid : 1; + bool mbHeightValid : 1; + bool mbPosXValid : 1; + bool mbPosYValid : 1; + bool mbClipValid : 1; + + bool mbFontFamilyValid : 1; + + bool mbRotationAngleValid : 1; + bool mbShearXAngleValid : 1; + bool mbShearYAngleValid : 1; + + bool mbAlphaValid : 1; + + bool mbCharScaleValid : 1; + + bool mbDimColorValid : 1; + bool mbFillColorValid : 1; + bool mbLineColorValid : 1; + bool mbCharColorValid : 1; + + bool mbFillStyleValid : 1; + bool mbLineStyleValid : 1; + bool mbCharWeightValid : 1; + bool mbUnderlineModeValid : 1; + bool mbCharPostureValid : 1; + bool mbVisibilityValid : 1; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEATTRIBUTELAYER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapeattributelayerholder.hxx b/slideshow/source/inc/shapeattributelayerholder.hxx new file mode 100644 index 000000000..4c123b4e5 --- /dev/null +++ b/slideshow/source/inc/shapeattributelayerholder.hxx @@ -0,0 +1,102 @@ +/* -*- 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_INC_SHAPEATTRIBUTELAYERHOLDER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEATTRIBUTELAYERHOLDER_HXX + +#include "attributableshape.hxx" +#include "shapeattributelayer.hxx" + +namespace slideshow::internal + { + /** Holds a ShapeAttributeLayer, together with the associated + Shape + + Use this class to hold ShapeAttributeLayer objects the + RAII way. When this object gets deleted, it will + automatically revoke the attribute layer for the given + shape (this encapsulates the somewhat clumsy notification + process that is required for shape and attribute layer + interaction). + */ + class ShapeAttributeLayerHolder + { + public: + /** Create a ShapeAttributeLayerHolder instance. + + This constructor creates an empty attribute holder, to + generate an attribute layer, you have to manually call + createAttributeLayer(). + */ + ShapeAttributeLayerHolder() : + mpShape(), + mpAttributeLayer() + { + } + + ~ShapeAttributeLayerHolder() + { + reset(); // ensures that the last attribute layer is + // correctly deregistered from the shape. + } + + ShapeAttributeLayerHolder(const ShapeAttributeLayerHolder&) = delete; + ShapeAttributeLayerHolder& operator=(const ShapeAttributeLayerHolder&) = delete; + + void reset() + { + if( mpShape && mpAttributeLayer ) + mpShape->revokeAttributeLayer( mpAttributeLayer ); + } + + /** This constructor receives a pointer to the Shape, from + which attribute layers should be generated. Initially, + this object does not create an attribute layer, you + have to manually call createAttributeLayer(). + + @param rShape + Shape for which attribute layers should be generated. + */ + bool createAttributeLayer( const AttributableShapeSharedPtr& rShape ) + { + reset(); + + mpShape = rShape; + + if( mpShape ) + mpAttributeLayer = mpShape->createAttributeLayer(); + + return static_cast< bool >(mpAttributeLayer); + } + + const ShapeAttributeLayerSharedPtr& get() const + { + return mpAttributeLayer; + } + + private: + AttributableShapeSharedPtr mpShape; + ShapeAttributeLayerSharedPtr mpAttributeLayer; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEATTRIBUTELAYERHOLDER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapeimporter.hxx b/slideshow/source/inc/shapeimporter.hxx new file mode 100644 index 000000000..5254e0a9f --- /dev/null +++ b/slideshow/source/inc/shapeimporter.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_INC_SHAPEIMPORTER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEIMPORTER_HXX + +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XLayer.hpp> + +#include <cppcanvas/canvasgraphic.hxx> + +#include "unoview.hxx" + +#include "shape.hxx" + +#include <stack> + +namespace slideshow::internal { + +struct SlideShowContext; + +typedef std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; +typedef std::shared_ptr< UnoView > UnoViewSharedPtr; +typedef std::vector< UnoViewSharedPtr > UnoViewVector; + +/** This class imports all shapes from a given XShapes object + */ +class ShapeImporter +{ +public: + /** Create shape importer. + + @param xPage + Page containing the shapes + + @param xActualPage + Actual page that's imported - if xPage is a master + page, this argument must refer to the using, i.e the + page that embeds this specific masterpage. Otherwise, + this argument is probably equal to xPage. + + @param nOrdNumStart + Each shape receives a z order number, in order of + import (which relies on the fact that the API returns + the shapes in draw order - which it does, + currently). Since we might mix several pages on screen + (e.g. master page and foreground page), this value can + be used as an offset to distinguish those pages. + + @param bConvertingMasterPage + When true, then the master page is imported. Otherwise, this + object imports the draw page. + */ + ShapeImporter( const css::uno::Reference< css::drawing::XDrawPage >& xPage, + const css::uno::Reference< css::drawing::XDrawPage >& xActualPage, + const css::uno::Reference< css::drawing::XDrawPagesSupplier>& xPagesSupplier, + const SlideShowContext& rContext, + sal_Int32 nOrdNumStart, + bool bConvertingMasterPage ); + + /** This method imports the presentation background shape + */ + ShapeSharedPtr importBackgroundShape(); // throw (ShapeLoadFailedException) + + /** This method imports presentation-visible shapes (and skips all others). + + @return the generated Shape, or NULL for no more shapes. + */ + ShapeSharedPtr importShape(); // throw (ConversionFailedException) + + /** Test whether import is done. + + @return true, if all shapes are imported via the + importShape() call. + */ + bool isImportDone() const; + const PolyPolygonVector& getPolygons() const; + + double getImportedShapesCount() const{ return mnAscendingPrio; } +private: + bool isSkip( css::uno::Reference<css::beans::XPropertySet> const& xPropSet, + std::u16string_view shapeType, + css::uno::Reference<css::drawing::XLayer> const& xLayer); + + ShapeSharedPtr createShape( + css::uno::Reference<css::drawing::XShape> const& xCurrShape, + css::uno::Reference<css::beans::XPropertySet> const& xPropSet, + std::u16string_view shapeType ) const; + + void importPolygons(css::uno::Reference< css::beans::XPropertySet > const& xPropSet) ; + + struct XShapesEntry + { + ShapeSharedPtr const mpGroupShape; + css::uno::Reference<css::drawing::XShapes> const mxShapes; + sal_Int32 const mnCount; + sal_Int32 mnPos; + + explicit XShapesEntry( ShapeSharedPtr const& pGroupShape ) + : mpGroupShape(pGroupShape), + mxShapes( pGroupShape->getXShape(), + css::uno::UNO_QUERY_THROW ), + mnCount(mxShapes->getCount()), mnPos(0) {} + explicit XShapesEntry( css::uno::Reference< + css::drawing::XShapes> const& xShapes ) + : mpGroupShape(), mxShapes(xShapes), + mnCount(xShapes->getCount()), mnPos(0) {} + }; + + css::uno::Reference<css::drawing::XDrawPage> mxPage; + css::uno::Reference<css::drawing::XDrawPagesSupplier> mxPagesSupplier; + const SlideShowContext& mrContext; + PolyPolygonVector maPolygons; + ::std::stack<XShapesEntry> maShapesStack; + double mnAscendingPrio; + bool mbConvertingMasterPage; +}; + +} // namespace presentation::internal + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapelistenereventhandler.hxx b/slideshow/source/inc/shapelistenereventhandler.hxx new file mode 100644 index 000000000..f8776e7c9 --- /dev/null +++ b/slideshow/source/inc/shapelistenereventhandler.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_SHAPELISTENEREVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPELISTENEREVENTHANDLER_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <memory> + +namespace com::sun::star { + namespace drawing { + class XShape; + } + namespace presentation { + class XShapeEventListener; + } +} + +/* Definition of ShapeListenerEventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling view events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle view events. + */ + class ShapeListenerEventHandler + { + public: + virtual ~ShapeListenerEventHandler() {} + + virtual bool listenerAdded( const css::uno::Reference<css::drawing::XShape>& xShape ) = 0; + + virtual bool listenerRemoved( const css::uno::Reference<css::drawing::XShape>& xShape ) = 0; + }; + + typedef ::std::shared_ptr< ShapeListenerEventHandler > ShapeListenerEventHandlerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPELISTENEREVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapemanager.hxx b/slideshow/source/inc/shapemanager.hxx new file mode 100644 index 000000000..28c89087d --- /dev/null +++ b/slideshow/source/inc/shapemanager.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEMANAGER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEMANAGER_HXX + +#include "disposable.hxx" +#include <com/sun/star/uno/Reference.hxx> +#include <memory> +#include <unordered_map> +#include "tools.hxx" + +namespace com::sun::star::drawing { class XShape; } + +/* Definition of ShapeManager interface */ + +namespace slideshow::internal + { + class HyperlinkArea; + class AnimatableShape; + class Shape; + typedef std::unordered_map< + css::uno::Reference< css::drawing::XShape >, + ShapeSharedPtr, + hash< css::uno::Reference< css::drawing::XShape > > + > XShapeToShapeMap; + typedef ::std::shared_ptr< AnimatableShape > AnimatableShapeSharedPtr; + typedef ::std::shared_ptr< Shape > ShapeSharedPtr; + typedef std::shared_ptr< HyperlinkArea > HyperlinkAreaSharedPtr; + + /** ShapeManager interface + + Implementers of this interface manage appearance and + animation of slideshow shapes. + */ + class ShapeManager : public Disposable + { + public: + /** Notify the ShapeManager that the given Shape starts an + animation now. + + This method enters animation mode for the Shape. If + the shape is already in animation mode, the call is + counted, and the shape only leaves animation mode + after a corresponding number of leaveAnimationMode() + calls. + */ + virtual void enterAnimationMode( const AnimatableShapeSharedPtr& rShape ) = 0; + + /** Notify the ShapeManager that the given Shape is no + longer animated. + + When called a corresponding number of times as + enterAnimationMode() for a given shape, this methods + ends animation mode for the given Shape. It is illegal + to call this method more often than + enterAnimationMode(). + */ + virtual void leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ) = 0; + + /** Notify that a shape needs an update + + This method notifies the ShapeManager that a shape + update is necessary. Use this if e.g. a running + animation changed the shape appearance. + + @param rShape + Shape which needs an update + */ + virtual void notifyShapeUpdate( const ShapeSharedPtr& rShape ) = 0; + + /** 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. + */ + virtual ShapeSharedPtr lookupShape( + css::uno::Reference< css::drawing::XShape > const & xShape ) const = 0; + + /** 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 + */ + virtual const XShapeToShapeMap& getXShapeToShapeMap() const = 0; + + /** Register given shape as a hyperlink target + + @param rArea + Hyperlink sensitive area. Will participate in + hyperlink region lookup. Must be in absolute user + space coordinates. + */ + virtual void addHyperlinkArea( const HyperlinkAreaSharedPtr& rArea ) = 0; + }; + + typedef ::std::shared_ptr< ShapeManager > ShapeManagerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapemaps.hxx b/slideshow/source/inc/shapemaps.hxx new file mode 100644 index 000000000..cb5ba0950 --- /dev/null +++ b/slideshow/source/inc/shapemaps.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_INC_SHAPEMAPS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEMAPS_HXX + +#include <comphelper/interfacecontainer3.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/presentation/XShapeEventListener.hpp> + +#include <memory> +#include <map> + +namespace com::sun::star::drawing { class XShape; } + +/* Definition of two shape maps */ + +namespace slideshow::internal + { + /// Maps XShape to shape listener + typedef ::std::map< css::uno::Reference< css::drawing::XShape>, + std::shared_ptr< ::comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener> > + > ShapeEventListenerMap; + + /// Maps XShape to mouse cursor + typedef ::std::map< css::uno::Reference< css::drawing::XShape>, + sal_Int16> ShapeCursorMap; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPEMAPS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/shapesubset.hxx b/slideshow/source/inc/shapesubset.hxx new file mode 100644 index 000000000..ad420ef55 --- /dev/null +++ b/slideshow/source/inc/shapesubset.hxx @@ -0,0 +1,145 @@ +/* -*- 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_INC_SHAPESUBSET_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SHAPESUBSET_HXX + +#include "subsettableshapemanager.hxx" +#include "doctreenode.hxx" + +#include <memory> + +namespace slideshow::internal + { + class ShapeSubset; + typedef ::std::shared_ptr< ShapeSubset > ShapeSubsetSharedPtr; + + /* Definition of ShapeSubset class */ + + /** Subset RAII wrapper for shapes. + + This class wraps the plain Shape with a wrapper for subset + functionality. Subsetting can be turned on and off. Note + that the reason to have shape subsetting RAII implemented + separately (i.e. not within the DrawShape) was that + subsetting (and de-subsetting) needs the + SubsettableShapeManager. And holding that at the DrawShape + creates one heck of a circular reference. + */ + class ShapeSubset + { + public: + /** Create a subset directly from a Shape. + + @param rOriginalShape + Original shape to subset + + @param rTreeNode + Subset this object should represent + + @param rShapeManager + Manager object, where subsets are + registered/unregistered + */ + ShapeSubset( const AttributableShapeSharedPtr& rOriginalShape, + const DocTreeNode& rTreeNode, + const SubsettableShapeManagerSharedPtr& rSubsetManager ); + + /** Create a subset from another subset. + + Note: if you want this subset to subtract from the + passed subset reference (and not from the original, + unsubsetted shape), the passed subset must be enabled + (enableSubsetShape() must have been called) + + @param rOriginalSubset + Original subset, which to subset again. + + @param rTreeNode + Subset of the original subset + */ + ShapeSubset( const ShapeSubsetSharedPtr& rOriginalSubset, + const DocTreeNode& rTreeNode ); + + /** Create full set for the given shape. + + @param rOriginalShape + Original shape, which will be represented as a whole + by this object + */ + ShapeSubset( const AttributableShapeSharedPtr& rOriginalShape, + const SubsettableShapeManagerSharedPtr& rShapeManager ); + + ~ShapeSubset(); + + // For a rationale for this hacky combination of user-provided dtor, defaulted copy + // ctor, and deleted copy assignment op, see the "TODO(Q1)" comment in + // CloningNodeCreator (slideshow/source/engine/animationnodes/animationnodefactory.cxx): + ShapeSubset(ShapeSubset const &) = default; + void operator =(ShapeSubset const &) = delete; + + /** Get the actual subset shape. + + If the subset is currently revoked, this method + returns the original shape. + */ + AttributableShapeSharedPtr const & getSubsetShape() const; + + /** Enable the subset shape. + + This method enables the subset. That means, on + successful completion of this method, the original + shape will cease to show the subset range, and + getSubsetShape() will return a valid shape. + */ + void enableSubsetShape(); + + /** Disable the subset shape. + + This method revokes the subset from the original + shape. That means, the original shape will again show + the hidden range. + */ + void disableSubsetShape(); + + /** Query whether this subset actually is none, but + contains the whole original shape's content + */ + bool isFullSet() const; + + /** Query subset this object represents + */ + const DocTreeNode& getSubset() const; + + private: + // default copy/assignment are okay + //ShapeSubset(const ShapeSubset&); + //ShapeSubset& operator=( const ShapeSubset& ); + + AttributableShapeSharedPtr mpOriginalShape; + AttributableShapeSharedPtr mpSubsetShape; + DocTreeNode maTreeNode; + SubsettableShapeManagerSharedPtr mpShapeManager; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SHAPESUBSET_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slide.hxx b/slideshow/source/inc/slide.hxx new file mode 100644 index 000000000..ea460582d --- /dev/null +++ b/slideshow/source/inc/slide.hxx @@ -0,0 +1,210 @@ +/* -*- 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_INC_SLIDE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SLIDE_HXX + +#include "unoviewcontainer.hxx" +#include "slidebitmap.hxx" +#include "shapemaps.hxx" + +#include <memory> + +namespace com::sun::star { + namespace drawing { + class XDrawPage; + class XDrawPagesSupplier; + } + namespace uno { + class XComponentContext; + } + namespace animations { + class XAnimationNode; + } +} + +namespace basegfx +{ + class B2IVector; +} + +/* Definition of Slide interface */ + +namespace slideshow::internal + { + class RGBColor; + class ScreenUpdater; + typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; + class Slide + { + public: + // Showing + + + /** Prepares to show slide. + + Call this method to reduce the timeout show(), and + getInitialSlideBitmap() need to complete. If + prefetch() is not called explicitly, the named + methods will call it implicitly. + */ + virtual void prefetch() = 0; + + /** Shows the slide on all registered views + + After this call, the slide will render itself to the + views, and start its animations. + + @param bSlideBackgroundPainted + When true, the initial slide content on the background + layer is already rendered (e.g. from a previous slide + transition). When false, Slide renders initial content of + slide. + */ + virtual void show( bool bSlideBackgroundPainted ) = 0; + + /** Force-ends the slide + + After this call, the slide has stopped all animations, + and ceased rendering/visualization on all views. + */ + virtual void hide() = 0; + + + // Queries + + + /** Query the size of this slide in user coordinates + + This value is retrieved from the XDrawPage properties. + */ + virtual basegfx::B2ISize getSlideSize() const = 0; + + /// Gets the underlying API page + virtual css::uno::Reference< css::drawing::XDrawPage > getXDrawPage() const = 0; + + /// Gets the animation node. + virtual css::uno::Reference< css::animations::XAnimationNode > getXAnimationNode() const = 0; + + ///Gets the slide Polygons + virtual PolyPolygonVector getPolygons() = 0; + + ///Draw the slide Polygons + virtual void drawPolygons() const = 0; + + ///Check if paint overlay is already active + virtual bool isPaintOverlayActive() const = 0; + + virtual void enablePaintOverlay() = 0; + + virtual void update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth ) = 0; + + // Slide bitmaps + + + /** Request bitmap for current slide appearance. + + The bitmap returned by this method depends on the + current state of the slide and the contained + animations. A newly generated slide will return the + initial slide content here (e.g. with all 'appear' + effect shapes invisible), a slide whose effects are + currently running will return a bitmap corresponding + to the current position on the animation timeline, and + a slide whose effects have all been run will generate + a bitmap with the final slide appearance (e.g. with + all 'hide' effect shapes invisible). + + @param rView + View to retrieve bitmap for (note that the bitmap will + have device-pixel equivalence to the content that + would have been rendered onto the given view). Note + that the view must have been added to this slide + before via viewAdded(). + */ + virtual SlideBitmapSharedPtr + getCurrentSlideBitmap( const UnoViewSharedPtr& rView ) const = 0; + + protected: + ~Slide() {} + }; + + typedef ::std::shared_ptr< Slide > SlideSharedPtr; + + class EventQueue; + class CursorManager; + class MediaFileManager; + class EventMultiplexer; + class ActivitiesQueue; + class UserEventQueue; + class RGBColor; + + /** Construct from XDrawPage + + The Slide object generally works in XDrawPage model + coordinates, that is, the page will have the width and + height as specified in the XDrawPage's property + set. The top, left corner of the page will be rendered + at (0,0) in the given canvas' view coordinate system. + + Does not render anything initially + + @param xDrawPage + Page to display on this slide + + @param xRootNode + Root of the SMIL animation tree. Used to animate the slide. + + @param rEventQueue + EventQueue. Used to post events. + + @param rActivitiesQueue + ActivitiesQueue. Used to run animations. + + @param rEventMultiplexer + Event source + + @param rUserEventQueue + UserEeventQueue + */ + SlideSharedPtr createSlide( const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage, + const css::uno::Reference< css::drawing::XDrawPagesSupplier >& xDrawPages, + const css::uno::Reference< css::animations::XAnimationNode >& xRootNode, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const ShapeEventListenerMap& rShapeListenerMap, + const ShapeCursorMap& rShapeCursorMap, + PolyPolygonVector&& rPolyPolygonVector, + RGBColor const& aUserPaintColor, + double dUserPaintStrokeWidth, + bool bUserPaintEnabled, + bool bIntrinsicAnimationsAllowed, + bool bDisableAnimationZOrder ); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SLIDE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slidebitmap.hxx b/slideshow/source/inc/slidebitmap.hxx new file mode 100644 index 000000000..ef3b41f2e --- /dev/null +++ b/slideshow/source/inc/slidebitmap.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_SLIDEBITMAP_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SLIDEBITMAP_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <cppcanvas/canvas.hxx> +#include <cppcanvas/bitmap.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <memory> + +namespace com::sun::star::rendering { class XBitmap; } + + +/* Definition of SlideBitmap class */ + +namespace slideshow::internal + { + + /** Little wrapper encapsulating an XBitmap + + This is to insulate us from changes to the preferred + transport format for bitmaps (using a sole XBitmap here is + a hack, since it is not guaranteed to work, or to work + without data loss, across different canvases). And since + we don't want to revert to a VCL Bitmap here, have to wait + until basegfx bitmap tooling is ready. + + TODO(F2): Add support for Canvas-independent bitmaps + here. Then, Slide::getInitialSlideBitmap and + Slide::getFinalSlideBitmap must also be adapted (they no + longer need a Canvas ptr, which is actually a hack now). + */ + class SlideBitmap + { + public: + explicit SlideBitmap( const ::cppcanvas::BitmapSharedPtr& rBitmap ); + SlideBitmap(const SlideBitmap&) = delete; + SlideBitmap& operator=(const SlideBitmap&) = delete; + + bool draw( const ::cppcanvas::CanvasSharedPtr& rCanvas ) const; + ::basegfx::B2ISize getSize() const; + void move( const ::basegfx::B2DPoint& rNewPos ); + void clip( const ::basegfx::B2DPolyPolygon& rClipPoly ); + + const css::uno::Reference< css::rendering::XBitmap >& getXBitmap() const; + + private: + ::basegfx::B2DPoint maOutputPos; + ::basegfx::B2DPolyPolygon maClipPoly; + + // TODO(Q2): Remove UNO bitmap as the transport medium + css::uno::Reference< css::rendering::XBitmap > mxBitmap; + }; + + typedef ::std::shared_ptr< SlideBitmap > SlideBitmapSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SLIDEBITMAP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slideshowcontext.hxx b/slideshow/source/inc/slideshowcontext.hxx new file mode 100644 index 000000000..565f0ff41 --- /dev/null +++ b/slideshow/source/inc/slideshowcontext.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_SLIDESHOWCONTEXT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SLIDESHOWCONTEXT_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <memory> + +namespace com::sun::star::uno { class XComponentContext; } +namespace box2d::utils { class box2DWorld; + typedef ::std::shared_ptr< box2DWorld > Box2DWorldSharedPtr; } + + + +namespace slideshow::internal + { + class ShapeManager; + class EventQueue; + class ActivitiesQueue; + class UserEventQueue; + class EventMultiplexer; + class ScreenUpdater; + class UnoViewContainer; + class CursorManager; + class MediaFileManager; + class SubsettableShapeManager; + typedef ::std::shared_ptr< SubsettableShapeManager > SubsettableShapeManagerSharedPtr; + + /** Common arguments for slideshow objects. + + This struct combines a number of object references + ubiquitously needed throughout the slideshow. + */ + struct SlideShowContext + { + /** Common context for node creation + + @param rShapeManager + ShapeManager, which handles all shapes + + @param rEventQueue + Event queue, where time-based events are to be + scheduled. A node must not schedule events there + before it's not resolved. + + @param rEventMultiplexer + Event multiplexer. Clients can register there for + about any event that happens in the slideshow + + @param rScreenUpdater + Screen updater. Gets notified of necessary screen + updates. + + @param rActivitiesQueue + Activities queue, where repeating activities are + to be scheduled. + + @param rMediaFileManager + To handle media file with package urls. + + @param rUserEventQueue + User event queue + + @param rViewContainer + Holds all views added to slideshow + + @param rComponentContext + To create UNO services from + */ + SlideShowContext( SubsettableShapeManagerSharedPtr& rSubsettableShapeManager, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer, + ScreenUpdater& rScreenUpdater, + ActivitiesQueue& rActivitiesQueue, + UserEventQueue& rUserEventQueue, + CursorManager& rCursorManager, + MediaFileManager& rMediaFileManager, + const UnoViewContainer& rViewContainer, + const css::uno::Reference< css::uno::XComponentContext>& rComponentContext, + box2d::utils::Box2DWorldSharedPtr& rBox2DWorldPtr ); + void dispose(); + + std::shared_ptr<SubsettableShapeManager>& mpSubsettableShapeManager; + EventQueue& mrEventQueue; + EventMultiplexer& mrEventMultiplexer; + ScreenUpdater& mrScreenUpdater; + ActivitiesQueue& mrActivitiesQueue; + UserEventQueue& mrUserEventQueue; + CursorManager& mrCursorManager; + MediaFileManager& mrMediaFileManager; + const UnoViewContainer& mrViewContainer; + css::uno::Reference< css::uno::XComponentContext> mxComponentContext; + box2d::utils::Box2DWorldSharedPtr& mpBox2DWorld; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SLIDESHOWCONTEXT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slideshowdllapi.h b/slideshow/source/inc/slideshowdllapi.h new file mode 100644 index 000000000..048a83dd2 --- /dev/null +++ b/slideshow/source/inc/slideshowdllapi.h @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/types.h> + +#if defined(SLIDESHOW_DLLIMPLEMENTATION) +#define SLIDESHOW_DLLPUBLIC SAL_DLLPUBLIC_EXPORT +#else +#define SLIDESHOW_DLLPUBLIC SAL_DLLPUBLIC_IMPORT +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slideshowexceptions.hxx b/slideshow/source/inc/slideshowexceptions.hxx new file mode 100644 index 000000000..58ce41bf6 --- /dev/null +++ b/slideshow/source/inc/slideshowexceptions.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_INC_SLIDESHOWEXCEPTIONS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SLIDESHOWEXCEPTIONS_HXX + + +namespace slideshow +{ + class SlideShowException {}; + + /** This exception is thrown, when the Shape class was not + able to convert an API object into our internal + representation. + */ + struct ShapeLoadFailedException : public SlideShowException {}; + + /** This exception is thrown, when the SMIL arithmetic expression + parser failed to parse a string. + */ + struct ParseError : public SlideShowException + { + ParseError() {} + explicit ParseError( const char* ) {} + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SLIDESHOWEXCEPTIONS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/slideview.hxx b/slideshow/source/inc/slideview.hxx new file mode 100644 index 000000000..bf95dc251 --- /dev/null +++ b/slideshow/source/inc/slideview.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_INC_SLIDEVIEW_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SLIDEVIEW_HXX + +#include "unoview.hxx" + +/* Definition of SlideView factory method */ +namespace slideshow::internal + { + class EventQueue; + class EventMultiplexer; + + /** Factory for SlideView + + @param xView + UNO slide view this object should encapsulate + + @param rEventQueue + Global event queue, to be used for notification + messages. + + @param rViewChangeFunc + Functor to call, when the UNO view signals a repaint. + */ + UnoViewSharedPtr createSlideView( + css::uno::Reference< css::presentation::XSlideShowView> const& xView, + EventQueue& rEventQueue, + EventMultiplexer& rEventMultiplexer ); +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SLIDEVIEW_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/smilfunctionparser.hxx b/slideshow/source/inc/smilfunctionparser.hxx new file mode 100644 index 000000000..d2ef96abf --- /dev/null +++ b/slideshow/source/inc/smilfunctionparser.hxx @@ -0,0 +1,155 @@ +/* -*- 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_INC_SMILFUNCTIONPARSER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SMILFUNCTIONPARSER_HXX + +#include "expressionnode.hxx" + +#include <basegfx/range/b2drectangle.hxx> +#include <rtl/ustring.hxx> + +#include <memory> + + +/* Definition of SmilFunctionParser class */ + +namespace slideshow::internal + { + class SmilFunctionParser + { + public: + SmilFunctionParser() = delete; + SmilFunctionParser(const SmilFunctionParser&) = delete; + SmilFunctionParser& operator=(const SmilFunctionParser&) = delete; + + /** Parse a string containing a SMIL value. + + This method parses a string representing + a fixed value (i.e. a value that does not + change by time). Due to the dynamic view + capabilities of the presentation engine, + this value can sometimes only be determined + during runtime of the animation (because + e.g. mixed screen/view coordinates are + involved), and is thus still returned as an + ExpressionNode object. An example for + such a case is the "Width+1.0" string, which + contains the width of the shape in user + coordinate space, and the screen width + in device coordinate space. + + The following grammar is accepted by this method: + <code> + 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 )* ) + + </code> + + @param rSmilValue + The string to parse + + @param rRelativeShapeBounds + The bounds of the shape this SMIL value is to be + evaluated for. The bounds must be <em>relative</em> to + the page the shape is part of, i.e. within the [0,1] + range. This is necessary, since the string might + contain symbolic references to the shape bounding box. + + @throws ParseError if an invalid expression is given. + + @return the generated function object. + */ + static std::shared_ptr<ExpressionNode> const & parseSmilValue( const OUString& rSmilValue, + const ::basegfx::B2DRectangle& rRelativeShapeBounds ); // throw ParseError + + /** Parse a string containing a SMIL function. + + This method parses a string representing + a possibly time-varying SMIL function. + + The following grammar is accepted by this method: + <code> + identifier = 't'|'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 )* ) + + </code> + + @param rSmilFunction + The string to parse + + @param rRelativeShapeBounds + The bounds of the shape this SMIL value is to be + evaluated for. The bounds must be <em>relative</em> to + the page the shape is part of, i.e. within the [0,1] + range. This is necessary, since the string might + contain symbolic references to the shape bounding box. + + @throws ParseError if an invalid expression is given. + + @return the generated function object. + */ + static std::shared_ptr<ExpressionNode> const & parseSmilFunction( const OUString& rSmilFunction, + const ::basegfx::B2DRectangle& rRelativeShapeBounds ); // throw ParseError + + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SMILFUNCTIONPARSER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/soundplayer.hxx b/slideshow/source/inc/soundplayer.hxx new file mode 100644 index 000000000..3914a08a2 --- /dev/null +++ b/slideshow/source/inc/soundplayer.hxx @@ -0,0 +1,115 @@ +/* -*- 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_INC_SOUNDPLAYER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SOUNDPLAYER_HXX + +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/media/XPlayer.hpp> +#include <avmedia/mediaitem.hxx> + +#include <memory> + +#include "pauseeventhandler.hxx" +#include "disposable.hxx" +#include "eventmultiplexer.hxx" + + +/* Definition of SoundPlayer class */ + +namespace slideshow::internal + { + class MediaFileManager; + + /** Little class that plays a sound from a URL. + TODO: + Must be explicitly disposed (as long as enable_shared_ptr_from_this + isn't available)! + */ + class SoundPlayer : public PauseEventHandler, + public Disposable + { + public: + /** Create a sound player object. + + @param rSoundURL + URL to a sound file. + + @param rComponentContext + Reference to a component context, used to create the + needed services + + @throws css::lang::NoSupportException, if + the sound file is invalid, or not supported by the + player service. + */ + static ::std::shared_ptr<SoundPlayer> create( + EventMultiplexer & rEventMultiplexer, + const OUString& rSoundURL, + const css::uno::Reference< css::uno::XComponentContext>& rComponentContext, + MediaFileManager& rMediaFileManager); + + virtual ~SoundPlayer() override; + + /** Query duration of sound playback. + + If the sound is already playing, this method + returns the remaining playback time. + + @return the playback duration in seconds. + */ + double getDuration() const; + + bool startPlayback(); + bool stopPlayback(); + bool isPlaying() const; + + void setPlaybackLoop( bool bLoop ); + + // PauseEventHandler: + virtual bool handlePause( bool bPauseShow ) override; + + // Disposable + virtual void dispose() override; + + private: + SoundPlayer( + EventMultiplexer & rEventMultiplexer, + const OUString& rSoundURL, + const css::uno::Reference< css::uno::XComponentContext>& rComponentContext, + MediaFileManager & rMediaFileManager); + + EventMultiplexer & mrEventMultiplexer; + // TODO(Q3): obsolete when boost::enable_shared_ptr_from_this + // is available + ::std::shared_ptr<SoundPlayer> mThis; + // Temp file for package url. + ::std::shared_ptr<::avmedia::MediaTempFile> mpMediaTempFile; + css::uno::Reference< css::media::XPlayer > mxPlayer; + }; + + typedef ::std::shared_ptr< SoundPlayer > SoundPlayerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SOUNDPLAYER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/stringanimation.hxx b/slideshow/source/inc/stringanimation.hxx new file mode 100644 index 000000000..09a8f859e --- /dev/null +++ b/slideshow/source/inc/stringanimation.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_INC_STRINGANIMATION_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_STRINGANIMATION_HXX + +#include "animation.hxx" +#include <rtl/ustring.hxx> + + +/* Definition of StringAnimation interface */ + +namespace slideshow::internal + { + /** Interface defining a string animation. + + This interface is a specialization of the Animation + interface, and is used to animate attributes representable + by a discrete character string (e.g. font names) + */ + class StringAnimation : public Animation + { + public: + typedef OUString ValueType; + + /** Set the animation to value rStr + + @param rStr + Current animation value. + */ + virtual bool operator()( const ValueType& rStr ) = 0; + + /** Request the underlying value for this animation. + + This is necessary for pure To or By animations, as the + Activity cannot determine a sensible start value + otherwise. + + @attention Note that you are only permitted to query + for the underlying value, if the animation has actually + been started (via start() call). + */ + virtual ValueType getUnderlyingValue() const = 0; + }; + + typedef ::std::shared_ptr< StringAnimation > StringAnimationSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_STRINGANIMATION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/subsettableshapemanager.hxx b/slideshow/source/inc/subsettableshapemanager.hxx new file mode 100644 index 000000000..9279bf017 --- /dev/null +++ b/slideshow/source/inc/subsettableshapemanager.hxx @@ -0,0 +1,111 @@ +/* -*- 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_INC_SUBSETTABLESHAPEMANAGER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_SUBSETTABLESHAPEMANAGER_HXX + +#include "shapemanager.hxx" +#include "intrinsicanimationeventhandler.hxx" +#include <memory> + +/* Definition of SubsettableShapeManager interface */ + +namespace slideshow::internal + { + class DocTreeNode; + class AttributableShape; + typedef ::std::shared_ptr< AttributableShape > AttributableShapeSharedPtr; + + /** SubsettableShapeManager interface + + Implementers of this interface manage creation and + revocation of shape subsets. Shape subsets are shapes that + represent (and animate) only parts of an original's shape + content. + */ + class SubsettableShapeManager : public ShapeManager + { + public: + /** 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. Calling this method multiple times with + the same original shape and DocTreeNode content always + returns the same shape. + + Requesting a subset from an original shape leads to + the original shape ceasing to display the subsetted + content. In other words, shape content is always + displayed in exactly one shape. + + @param rOrigShape + The shape the subset is to be created for + + @param rSubsetShape + The subset to display in the generated shape. + */ + virtual AttributableShapeSharedPtr getSubsetShape( + const AttributableShapeSharedPtr& rOrigShape, + const DocTreeNode& rTreeNode ) = 0; + + /** 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 + */ + virtual void revokeSubset( + const AttributableShapeSharedPtr& rOrigShape, + const AttributableShapeSharedPtr& rSubsetShape ) = 0; + + // Evil hackish way of getting intrinsic animation slide-wise + + /** Register an event handler that will be called when + user paint parameters change. + + @param rHandler + Handler to call when a shape listener changes + */ + virtual void addIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) = 0; + virtual void removeIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) = 0; + + /** Notify that shape-intrinsic animations are now enabled. + */ + virtual void notifyIntrinsicAnimationsEnabled() = 0; + + /** Notify that shape-intrinsic animations are now disabled. + */ + virtual void notifyIntrinsicAnimationsDisabled() = 0; + }; + + typedef ::std::shared_ptr< SubsettableShapeManager > SubsettableShapeManagerSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_SUBSETTABLESHAPEMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/tools.hxx b/slideshow/source/inc/tools.hxx new file mode 100644 index 000000000..edfefe246 --- /dev/null +++ b/slideshow/source/inc/tools.hxx @@ -0,0 +1,396 @@ +/* -*- 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_INC_TOOLS_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_TOOLS_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/animations/XAnimationNode.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <comphelper/random.hxx> +#include <sal/log.hxx> + +#include <cppcanvas/color.hxx> + +#include "shapeattributelayer.hxx" +#include "shape.hxx" +#include "rgbcolor.hxx" +#include "hslcolor.hxx" + +#include <memory> + +namespace com::sun::star::beans { struct NamedValue; } +namespace basegfx +{ + class B2DRange; + class B2DVector; + class B2IVector; + class B2DHomMatrix; + typedef B2IVector B2ISize; +} +namespace cppcanvas{ class Canvas; } + +class GDIMetaFile; + +/* Definition of some animation tools */ +namespace slideshow +{ + namespace internal + { + class UnoView; + class Shape; + class ShapeAttributeLayer; + typedef std::shared_ptr< UnoView > UnoViewSharedPtr; + typedef std::shared_ptr< GDIMetaFile > GDIMetaFileSharedPtr; + + template <typename T> + inline ::std::size_t hash_value( T const * p ) + { + ::std::size_t d = static_cast< ::std::size_t >( + reinterpret_cast< ::std::ptrdiff_t >(p) ); + return d + (d >> 3); + } + + template <typename T> + struct hash + { + ::std::size_t operator()( T const& val ) const { + return hash_value(val); + } + }; + } +} + +namespace com::sun::star::uno { + + template <typename T> + inline ::std::size_t hash_value( + css::uno::Reference<T> const& x ) + { + // normalize to object root, because _only_ XInterface is defined + // to be stable during object lifetime: + css::uno::Reference< css::uno::XInterface> const xRoot( x, css::uno::UNO_QUERY ); + return slideshow::internal::hash<void *>()(xRoot.get()); + } + +} + +namespace slideshow +{ + namespace internal + { + // Value extraction from Any + // ========================= + + /// extract unary double value from Any + bool extractValue( double& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract int from Any + bool extractValue( sal_Int32& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract enum/constant group value from Any + bool extractValue( sal_Int16& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract color value from Any + bool extractValue( RGBColor& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract color value from Any + bool extractValue( HSLColor& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract plain string from Any + bool extractValue( OUString& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract bool value from Any + bool extractValue( bool& o_rValue, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /// extract double 2-tuple from Any + bool extractValue( basegfx::B2DTuple& o_rPair, + const css::uno::Any& rSourceAny, + const ShapeSharedPtr& rShape, + const basegfx::B2DVector& rSlideBounds ); + + /** Search a sequence of NamedValues for a given element. + + @return true, if the sequence contains the specified + element. + */ + bool findNamedValue( css::uno::Sequence< css::beans::NamedValue > const& rSequence, + const css::beans::NamedValue& rSearchKey ); + + basegfx::B2DRange calcRelativeShapeBounds( const basegfx::B2DVector& rPageSize, + const basegfx::B2DRange& rShapeBounds ); + + /** Get the shape transformation from the attribute set + + @param rBounds + Original shape bound rect (to substitute default attribute + layer values) + + @param pAttr + Attribute set. Might be NULL (then, rBounds is used to set + a simple scale and translate of the unit rect to rBounds). + */ + basegfx::B2DHomMatrix getShapeTransformation( + const basegfx::B2DRectangle& rBounds, + const ShapeAttributeLayerSharedPtr& pAttr ); + + /** Get a shape's sprite transformation from the attribute set + + @param rPixelSize + Pixel size of the sprite + + @param rOrigSize + Original shape size (i.e. the size of the actual sprite + content, in the user coordinate system) + + @param pAttr + Attribute set. Might be NULL (then, rBounds is used to set + a simple scale and translate of the unit rect to rBounds). + + @return the transformation to be applied to the sprite. + */ + basegfx::B2DHomMatrix getSpriteTransformation( + const basegfx::B2DVector& rPixelSize, + const basegfx::B2DVector& rOrigSize, + const ShapeAttributeLayerSharedPtr& pAttr ); + + /** Calc update area for a shape. + + This method calculates the 'covered' area for the shape, + i.e. the rectangle that is affected when rendering the + shape. Apart from applying the given transformation to the + shape rectangle, this method also takes attributes into + account, which further scale the output (e.g. character + sizes). + + @param rUnitBounds + Shape bounds, in the unit rect coordinate space + + @param rShapeTransform + Transformation matrix the shape should undergo. + + @param pAttr + Current shape attributes + */ + basegfx::B2DRectangle getShapeUpdateArea( + const basegfx::B2DRectangle& rUnitBounds, + const basegfx::B2DHomMatrix& rShapeTransform, + const ShapeAttributeLayerSharedPtr& pAttr ); + + /** Calc update area for a shape. + + This method calculates the 'covered' area for the shape, + i.e. the rectangle that is affected when rendering the + shape. The difference from the other getShapeUpdateArea() + method is the fact that this one works without + ShapeAttributeLayer, and only scales up the given shape + user coordinate bound rect. The method is typically used + to retrieve user coordinate system bound rects for shapes + which are smaller than the default unit bound rect + (because e.g. of subsetting) + + @param rUnitBounds + Shape bounds, in the unit rect coordinate space + + @param rShapeBounds + Current shape bounding box in user coordinate space. + */ + basegfx::B2DRange getShapeUpdateArea( const basegfx::B2DRange& rUnitBounds, + const basegfx::B2DRange& rShapeBounds ); + + /** Calc output position and size of shape, according to given + attribute layer. + + Rotations, shears etc. and not taken into account, + i.e. the returned rectangle is NOT the bounding box. Use + it as if aBounds.getMinimum() is the output position and + aBounds.getRange() the scaling of the shape. + */ + basegfx::B2DRectangle getShapePosSize( + const basegfx::B2DRectangle& rOrigBounds, + const ShapeAttributeLayerSharedPtr& pAttr ); + + /** Convert a plain UNO API 32 bit int to RGBColor + */ + RGBColor unoColor2RGBColor( sal_Int32 ); + /** Convert an IntSRGBA to plain UNO API 32 bit int + */ + sal_Int32 RGBAColor2UnoColor( cppcanvas::IntSRGBA ); + + /** Fill a plain rectangle on the given canvas with the given color + */ + void fillRect( const cppcanvas::CanvasSharedPtr& rCanvas, + const basegfx::B2DRectangle& rRect, + cppcanvas::IntSRGBA aFillColor ); + + /** Init canvas with default background (white) + */ + void initSlideBackground( const cppcanvas::CanvasSharedPtr& rCanvas, + const basegfx::B2ISize& rSize ); + + /// Gets a random ordinal [0,n) + inline ::std::size_t getRandomOrdinal( const ::std::size_t n ) + { + return comphelper::rng::uniform_size_distribution(0, n-1); + } + + template <typename ValueType> + inline bool getPropertyValue( + ValueType & rValue, + css::uno::Reference< + css::beans::XPropertySet> const & xPropSet, + OUString const & propName ) + { + try { + const css::uno::Any& a( + xPropSet->getPropertyValue( propName ) ); + bool const bRet = css::uno::fromAny(a, &rValue); +#if OSL_DEBUG_LEVEL > 0 + if( !bRet ) + SAL_INFO("slideshow", __func__ << ": while retrieving property " << propName << ", cannot extract Any of type " + << a.getValueTypeRef()->pTypeName); +#endif + return bRet; + } + catch (css::uno::RuntimeException &) + { + throw; + } + catch (css::uno::Exception &) + { + return false; + } + } + + template <typename ValueType> + inline bool getPropertyValue( + css::uno::Reference< ValueType >& rIfc, + css::uno::Reference< css::beans::XPropertySet> const & xPropSet, + OUString const & propName ) + { + try + { + const css::uno::Any& a(xPropSet->getPropertyValue( propName )); + rIfc.set( a, css::uno::UNO_QUERY ); + + bool const bRet = rIfc.is(); +#if OSL_DEBUG_LEVEL > 0 + if( !bRet ) + SAL_INFO("slideshow", __func__ << ": while retrieving property " << propName << ", cannot extract Any of type " + << a.getValueTypeRef()->pTypeName << " to interface"); +#endif + return bRet; + } + catch (css::uno::RuntimeException &) + { + throw; + } + catch (css::uno::Exception &) + { + return false; + } + } + + /// Get the content of the BoundRect shape property + basegfx::B2DRectangle getAPIShapeBounds( const css::uno::Reference< css::drawing::XShape >& xShape ); + +/* + TODO(F1): When ZOrder someday becomes usable enable this + + /// Get the content of the ZOrder shape property + double getAPIShapePrio( const css::uno::Reference< css::drawing::XShape >& xShape ); +*/ + + basegfx::B2IVector getSlideSizePixel( const basegfx::B2DVector& rSize, + const UnoViewSharedPtr& pView ); + } + + // TODO(Q1): this could possibly be implemented with a somewhat + // more lightweight template, by having the actual worker receive + // only a function pointer, and a thin templated wrapper around + // that which converts member functions into that. + + /** Apply given functor to every animation node child. + + @param xNode + Parent node + + @param rFunctor + Functor to apply. The functor must have an appropriate + operator()( const css::uno::Reference< css::animations::XAnimationNode >& ) member. + + @return true, if the functor was successfully applied to + all children, false otherwise. + */ + template< typename Functor > inline bool for_each_childNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode, + Functor& rFunctor ) + { + try + { + // get an XEnumerationAccess to the children + css::uno::Reference< css::container::XEnumerationAccess > + xEnumerationAccess( xNode, + css::uno::UNO_QUERY_THROW ); + css::uno::Reference< css::container::XEnumeration > + xEnumeration( xEnumerationAccess->createEnumeration(), + css::uno::UNO_SET_THROW ); + + while( xEnumeration->hasMoreElements() ) + { + css::uno::Reference< css::animations::XAnimationNode > + xChildNode( xEnumeration->nextElement(), + css::uno::UNO_QUERY_THROW ); + rFunctor( xChildNode ); + } + return true; + } + catch( css::uno::Exception& ) + { + return false; + } + } +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_TOOLS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/transitionfactory.hxx b/slideshow/source/inc/transitionfactory.hxx new file mode 100644 index 000000000..ea645cbec --- /dev/null +++ b/slideshow/source/inc/transitionfactory.hxx @@ -0,0 +1,115 @@ +/* -*- 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_INC_TRANSITIONFACTORY_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_TRANSITIONFACTORY_HXX + +#include <com/sun/star/animations/XTransitionFilter.hpp> +#include <com/sun/star/presentation/XTransitionFactory.hpp> + +#include "rgbcolor.hxx" +#include "slide.hxx" +#include "screenupdater.hxx" +#include "animationactivity.hxx" +#include "activitiesfactory.hxx" +#include "numberanimation.hxx" +#include "soundplayer.hxx" +#include "shapemanager.hxx" + +namespace slideshow::internal + { + /* Definition of Transitionfactory class */ + namespace TransitionFactory + { + /** Create a transition effect for shapes. + + This method creates an AnimationActivity, which, when + run, performs the requested transition effect on the + given shape. + + @param rParms + Collection of activity parameters, see ActivitiesFactory + + @param rShape + Shape to animate + + @param rShapeManager + ShapeManager, to manage shape animation + + @param xTransition + The transition effect + + @return the created activity, or NULL for no + transition effect + */ + AnimationActivitySharedPtr createShapeTransition( + const ActivitiesFactory::CommonParameters& rParms, + const AnimatableShapeSharedPtr& rShape, + const ShapeManagerSharedPtr& rShapeManager, + const ::basegfx::B2DVector& rSlideSize, + css::uno::Reference< css::animations::XTransitionFilter > const& xTransition ); + + + /** Create a transition effect for slides. + + This method creates a NumberAnimation, which, + when run, performs the requested transition effect + with the slide bitmaps. + + @param rEnteringBitmap + Bitmap of the slide which 'enters' the screen. + + @param rLeavingBitmap + Bitmap of the slide which 'leaves' the screen. + + @param nTransitionType + Type of the transition (see XTransitionFilter) + + @param nTransitionSubType + Subtype of the transition (see XTransitionFilter) + + @param bTransitionDirection + Direction of the transition (see XTransitionFilter) + + @param rTransitionFadeColor + Optional fade color for the transition + + @return the created animation, or NULL for no + transition effect + */ + NumberAnimationSharedPtr createSlideTransition( + const SlideSharedPtr& rLeavingSlide, + const SlideSharedPtr& rEnteringSlide, + const UnoViewContainer& rViewContainer, + ScreenUpdater& rScreenUpdater, + EventMultiplexer& rEventMultiplexer, + const css::uno::Reference< css::presentation::XTransitionFactory>& + xOptionalFactory, + sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType, + bool bTransitionDirection, + const RGBColor& rTransitionFadeColor, + const SoundPlayerSharedPtr& rSoundPlayer ); + } + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_TRANSITIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/transitioninfo.hxx b/slideshow/source/inc/transitioninfo.hxx new file mode 100644 index 000000000..c0cd0aed1 --- /dev/null +++ b/slideshow/source/inc/transitioninfo.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_INC_TRANSITIONINFO_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_TRANSITIONINFO_HXX + +#include <sal/types.h> + + +namespace slideshow::internal { + +struct TransitionInfo +{ + // the following two member serve as the search key + // for an incoming XTransitionFilter node + + // { + + sal_Int16 mnTransitionType; + sal_Int16 mnTransitionSubType; + + // } + + + /** This enum classifies a transition type + */ + enum TransitionClass + { + /// Invalid type + TRANSITION_INVALID, + + /// Transition expressed by parametric clip polygon + TRANSITION_CLIP_POLYPOLYGON, + + /// Transition expressed by hand-crafted function + TRANSITION_SPECIAL + }; + + /// class of effect handling + TransitionClass meTransitionClass; + + /// Rotation angle of clip polygon + double mnRotationAngle; + + /// X scaling of clip polygon (negative values mirror) + double mnScaleX; + + /// Y scaling of clip polygon (negative values mirror) + double mnScaleY; + + /** This enum determines the method how to reverse + a parametric clip polygon transition. + + A reversed transition runs in the geometrically + opposite direction. For a left-to-right bar wipe, the + reversed transition is a right-to-left wipe, whereas + for an iris transition, the reversed mode will show + the target in the outer area (instead of in the inner + area, as in normal mode). + */ + enum class ReverseMethod + { + /** Ignore direction attribute altogether + (if it has no sensible meaning for this transition) + */ + Ignore, + + /** Combination of ReverseMethod::InvertSweep and + ReverseMethod::SubtractPolygon. + */ + SubtractAndInvert, + + /// Reverse by rotating polygon 180 degrees + Rotate180, + + /// Reverse by flipping polygon at the y (!) axis + FlipX, + + /// Reverse by flipping polygon at the x (!) axis + FlipY + }; + + /** Indicating the method to use when transition + should be 'reversed'. + + @see ReverseMethod + */ + ReverseMethod meReverseMethod; + + /** When true, transition 'out' effects are realized + by inverting the parameter sweep direction (1->0 + instead of 0->1). Otherwise, 'out' effects are + realized by changing inside and outside areas of + the parametric poly-polygon. + */ + bool mbOutInvertsSweep; + + /** when true, scale clip polygon isotropically to + target size. when false, scale is + anisotropically. + */ + bool mbScaleIsotrophically; + + + /// Compare against type and subtype + class Comparator + { + sal_Int16 mnTransitionType; + sal_Int16 mnTransitionSubType; + public: + Comparator( sal_Int16 nTransitionType, + sal_Int16 nTransitionSubType ) + : mnTransitionType( nTransitionType ), + mnTransitionSubType( nTransitionSubType ) {} + bool operator()( const TransitionInfo& rEntry ) const { + return rEntry.mnTransitionType == mnTransitionType && + rEntry.mnTransitionSubType == mnTransitionSubType; + } + }; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_TRANSITIONINFO_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/unoview.hxx b/slideshow/source/inc/unoview.hxx new file mode 100644 index 000000000..7adec4c15 --- /dev/null +++ b/slideshow/source/inc/unoview.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_INC_UNOVIEW_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_UNOVIEW_HXX + +#include "view.hxx" +#include <com/sun/star/uno/Reference.hxx> + +#include <vector> + +namespace com::sun::star::presentation { class XSlideShowView; } + + +/* Definition of UnoView interface */ + +namespace slideshow::internal + { + /** Extend View with UNO interface retrieval. + + This interface extends View with a UNO interface + retrieval, to be used for Views which are set from + external API. + */ + class UnoView : public View + { + public: + /** Retrieve the underlying UNO slide view. + */ + virtual css::uno::Reference< css::presentation::XSlideShowView > getUnoView() const = 0; + + /** Dispose view + + This needs to be different from Disposable interface, + as the UNO XComponent also provides a dispose() (only + with a different calling convention under Windows). + */ + virtual void _dispose() = 0; + + /** Return whether the sound play back is enabled. + */ + virtual bool isSoundEnabled() const = 0; + + /** Tell the view whether it may play sounds. Disabling this + can be used to prevent different views to play the same + sounds at the same time. + */ + virtual void setIsSoundEnabled (const bool bValue) = 0; + }; + + typedef std::shared_ptr< UnoView > UnoViewSharedPtr; + typedef std::vector< UnoViewSharedPtr > UnoViewVector; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_UNOVIEW_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/unoviewcontainer.hxx b/slideshow/source/inc/unoviewcontainer.hxx new file mode 100644 index 000000000..54ee9d579 --- /dev/null +++ b/slideshow/source/inc/unoviewcontainer.hxx @@ -0,0 +1,81 @@ +/* -*- 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_INC_UNOVIEWCONTAINER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_UNOVIEWCONTAINER_HXX + +#include <com/sun/star/uno/Reference.hxx> + +#include "unoview.hxx" + + +namespace com::sun::star::presentation { class XSlideShowView; } + +/* Definition of UnoViewContainer class */ + +namespace slideshow::internal + { + /** Contains UnoViews + */ + class UnoViewContainer + { + public: + UnoViewContainer(); + UnoViewContainer(const UnoViewContainer&) = delete; + UnoViewContainer& operator=(const UnoViewContainer&) = delete; + + /** Add a view to this container + + @return true, if the view was successfully added + (false is e.g. returned, if the view was already + added) + */ + bool addView( const UnoViewSharedPtr& rView ); + + /** Remove a previously added a view from this container + + @return the View object, if this view was successfully + removed, and an empty shared_ptr otherwise (e.g. if + this view wasn't added in the first place) + */ + UnoViewSharedPtr removeView( const css::uno::Reference<css::presentation::XSlideShowView >& xView ); + + /// Dispose all stored views. Implies clear(). + void dispose(); + + // the following parrots STL container concept methods + // =================================================== + + bool empty() const { return maViews.empty(); } + + UnoViewVector::iterator begin() { return maViews.begin(); } + UnoViewVector::const_iterator begin() const { return maViews.begin(); } + UnoViewVector::iterator end() { return maViews.end(); } + UnoViewVector::const_iterator end() const { return maViews.end(); } + + private: + /// All added views + UnoViewVector maViews; + }; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_UNOVIEWCONTAINER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/usereventqueue.hxx b/slideshow/source/inc/usereventqueue.hxx new file mode 100644 index 000000000..785a8bdf3 --- /dev/null +++ b/slideshow/source/inc/usereventqueue.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_USEREVENTQUEUE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_USEREVENTQUEUE_HXX + +#include <com/sun/star/animations/XAnimationNode.hpp> + +#include "eventmultiplexer.hxx" +#include "eventqueue.hxx" +#include "shape.hxx" + +/* Definition of UserEventQueue class */ + +namespace slideshow::internal { + +class AllAnimationEventHandler; +class ShapeClickEventHandler; +class ClickEventHandler; +class CursorManager; +class SkipEffectEventHandler; +class RewindEffectEventHandler; +class MouseEnterHandler; +class MouseLeaveHandler; + +/** This class schedules user-activated events. + + This class registers at the EventMultiplexer and fires + events registered for certain user actions. Note that all + events will not be fired immediately after the user action + occurred, but always added to the EventQueue (and fired the + next time that queue is processed). Which is actually a + feature. + + Conceptually, an event is an object that typically is + fired only once. After that, the event is exhausted, and + should be discarded. Therefore, all events registered on + this object are fired and then all references to them are + removed. +*/ +class UserEventQueue +{ +public: + /** Create a user event queue + + @param rEventMultiplexer + The slideshow-global event source, where this class + registers its event handlers. + + @param rEventQueue + Reference to the main event queue. Since we hold this + object by plain reference, it must live longer than we + do. On the other hand, that queue must not fire events + after this object is destroyed, since we might + schedule events there which itself contain plain + references to this object. Basically, EventQueue and + UserEventQueue should have the same lifetime, and since + this is not possible, both must be destructed in a + phased mode: first clear both of any remaining events, + then destruct them. + */ + UserEventQueue( EventMultiplexer& rMultiplexer, + EventQueue& rEventQueue, + CursorManager& rCursorManager ); + ~UserEventQueue(); + UserEventQueue(const UserEventQueue&) = delete; + UserEventQueue& operator=(const UserEventQueue&) = delete; + + /** Clear all registered events. + + This method clears all registered, but + not-yet-executed events. This comes in handy when + force-ending a slide, to avoid interference with the + next slide's event registration. + */ + void clear(); + + /** Set advance on click behaviour. + + @param bAdvanceOnClick + When true, a click somewhere on the slide will also + generate next effect event. In this case, it is + irrelevant where on the slide the mouse is clicked, + i.e. the shape need not be hit by the mouse. + */ + void setAdvanceOnClick( bool bAdvanceOnClick ); + + /** Register an event that will be fired when the given + animation node starts. + + Note that <em>all</em> registered events will be fired + when the animation start occurs. This is in contrast to + the mouse events below. + */ + void registerAnimationStartEvent( + const EventSharedPtr& rEvent, + const css::uno::Reference<css::animations::XAnimationNode>& xNode ); + + /** Register an event that will be fired when the given + animation node ends its active duration. + + Note that <em>all</em> registered events will be fired + when the animation end occurs. This is in contrast to + the mouse events below. + */ + void registerAnimationEndEvent( + const EventSharedPtr& rEvent, + const css::uno::Reference<css::animations::XAnimationNode>& xNode ); + + /** Register an event that will be fired when audio output + stopped for the given animation node. + + Note that <em>all</em> registered events will be fired + when the audio stopping occurs. This is in contrast to + the mouse events below. + */ + void registerAudioStoppedEvent( + const EventSharedPtr& rEvent, + const css::uno::Reference<css::animations::XAnimationNode>& xNode ); + + /** Register an event that is fired when a shape is clicked + + For every mouse click, only one of the events + registered here is fired. The order of fired events is + the order of registration, i.e. the first event + registered will be the one fired for the first mouse + click on the given shape. + */ + void registerShapeClickEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ); + + /** Registers an event that is fired when the current effects(s) + are skipped, .e.g. when the left mouse button is pressed. + Then, all registered events are fired and removed from this + queue. After firing, a next effect event is issued to this + queue to start the next effect. + @param pEvent + The event to execute when skipping the current effect. + @param bSkipTriggersNextEffect + When <TRUE/> then after skipping the current effect the next + effect is triggered. When <FALSE/> then the next effect is not + triggered. + */ + void registerSkipEffectEvent( + EventSharedPtr const& pEvent, + const bool bSkipTriggersNextEffect); + + /** Register an event that is fired to show the next event + + For every next effect event, only one of the events + registered here is fired. The order of fired events is + the order of registration, i.e. the first event + registered will be the one fired for the first mouse + click. When advance-on-click (see method + setAdvanceOnClick()) is enabled, a mouse click + somewhere on the slide will also generate a next + effect event. In this case, it is irrelevant where on + the slide the mouse is clicked, i.e. the shape need + not be hit by the mouse. + */ + void registerNextEffectEvent( const EventSharedPtr& rEvent ); + + /** Register an event that is fired on a double mouse + click on a shape + + For every mouse double click, only one of the events + registered here is fired. The order of fired events is + the order of registration, i.e. the first event + registered will be the one fired for the first mouse + double click. It is irrelevant where on the slide the + mouse is clicked, i.e. the shape need not be hit by + the mouse. + */ + void registerShapeDoubleClickEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ); + + /** Register an event that is fired when the mouse enters + the area of the given shape + + For every enter, only one of the events registered + here is fired. The order of fired events is the order + of registration, i.e. the first event registered will + be the one fired for the first time the mouse enters + the given shape. + */ + void registerMouseEnterEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ); + + /** Register an event that is fired when the mouse leaves + the area of the given shape + + For every leave, only one of the events registered + here is fired. The order of fired events is the order + of registration, i.e. the first event registered will + be the one fired for the first time the mouse leaves + the given shape area. + */ + void registerMouseLeaveEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ); + + /** Typically skipping the current effect is triggered by mouse clicks + or key presses that trigger the next effect. This method allows the + skipping of effects to be triggered programmatically. + */ + void callSkipEffectEventHandler(); + +private: + /** Generically register an event on one of the handlers. + + If the handler is not yet created, do that and + register it via the Functor + */ + template< typename Handler, typename Functor > + void registerEvent( ::std::shared_ptr< Handler >& rHandler, + const EventSharedPtr& rEvent, + const Functor& rRegistrationFunctor ); + + /** Generically register an event on one of the handlers. + + If the handler is not yet created, do that and + register it via the Functor. This version of the + registerEvent method takes an additional parameter + rArg, which is passed as the second argument to + rHandler's addEvent() method. + */ + template< typename Handler, typename Arg, typename Functor > + void registerEvent( ::std::shared_ptr< Handler >& rHandler, + const EventSharedPtr& rEvent, + const Arg& rArg, + const Functor& rRegistrationFunctor ); + + EventMultiplexer& mrMultiplexer; + EventQueue& mrEventQueue; + CursorManager& mrCursorManager; + + ::std::shared_ptr<AllAnimationEventHandler> mpAnimationStartEventHandler; + ::std::shared_ptr<AllAnimationEventHandler> mpAnimationEndEventHandler; + ::std::shared_ptr<AllAnimationEventHandler> mpAudioStoppedEventHandler; + ::std::shared_ptr<ShapeClickEventHandler> mpShapeClickEventHandler; + ::std::shared_ptr<ClickEventHandler> mpClickEventHandler; + ::std::shared_ptr<SkipEffectEventHandler> mpSkipEffectEventHandler; + ::std::shared_ptr<ShapeClickEventHandler> mpShapeDoubleClickEventHandler; + ::std::shared_ptr<MouseEnterHandler> mpMouseEnterHandler; + ::std::shared_ptr<MouseLeaveHandler> mpMouseLeaveHandler; + + bool mbAdvanceOnClick; +}; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_USEREVENTQUEUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/view.hxx b/slideshow/source/inc/view.hxx new file mode 100644 index 000000000..0d1937b60 --- /dev/null +++ b/slideshow/source/inc/view.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_VIEW_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_VIEW_HXX + +#include "viewlayer.hxx" + +#include <memory> +#include <vector> + + +namespace basegfx { class B2DRange; class B2DVector; } + + +/* Definition of View interface */ + +namespace slideshow::internal + { + class View : public ViewLayer + { + public: + /** Create a new view layer for this view + + @param rLayerBounds + Specifies the bound rect of the layer relative to the + user view coordinate system. + + This method sets the bounds of the view layer in + document coordinates (i.e. 'logical' coordinates). The + resulting transformation is then concatenated with the + underlying view transformation, returned by the + getTransformation() method. + */ + virtual ViewLayerSharedPtr createViewLayer( const basegfx::B2DRange& rLayerBounds ) const = 0; + + /** Update screen representation from backbuffer + */ + virtual bool updateScreen() const = 0; + + /** Paint screen content unconditionally from backbuffer + */ + virtual bool paintScreen() const = 0; + + /** Set the size of the user view coordinate system. + + This method sets the width and height of the view in + document coordinates (i.e. 'logical' coordinates). The + resulting transformation is then concatenated with the + underlying view transformation, returned by the + getTransformation() method. + */ + virtual void setViewSize( const ::basegfx::B2DSize& ) = 0; + + /** Change the view's mouse cursor. + + @param nPointerShape + One of the css::awt::SystemPointer + constant group members. + */ + virtual void setCursorShape( sal_Int16 nPointerShape ) = 0; + }; + + typedef std::shared_ptr< View > ViewSharedPtr; + typedef std::vector< ViewSharedPtr > ViewVector; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_VIEW_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/vieweventhandler.hxx b/slideshow/source/inc/vieweventhandler.hxx new file mode 100644 index 000000000..a375e047f --- /dev/null +++ b/slideshow/source/inc/vieweventhandler.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_VIEWEVENTHANDLER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_VIEWEVENTHANDLER_HXX + +#include <memory> + +#include "unoview.hxx" +#include "disposable.hxx" + + +/* Definition of ViewEventHandler interface */ + +namespace slideshow::internal + { + + /** Interface for handling view events. + + Classes implementing this interface can be added to an + EventMultiplexer object, and are called from there to + handle view events. + */ + class ViewEventHandler : public virtual SharedPtrAble + { + public: + /** Notify new view. + + @param rView + The newly added view + */ + virtual void viewAdded( const UnoViewSharedPtr& rView ) = 0; + + /** Notify removed view. + + @param rView + The removed view + */ + virtual void viewRemoved( const UnoViewSharedPtr& rView ) = 0; + + /** Notify changed view. + + Reasons for a viewChanged notification can be + different view size, transformation, or other device + properties (color resolution or profile, etc.) + + @param rView + The changed view + */ + virtual void viewChanged( const UnoViewSharedPtr& rView ) = 0; + + /** Notify that all views changed. + + Reasons for a viewChanged notification can be + different view size, transformation, or other device + properties (color resolution or profile, etc.) + + Note that this method avoids hidden inefficiencies + (O(n^2) behaviour when viewChanged() needs to perform + linear searches) + */ + virtual void viewsChanged() = 0; + }; + + typedef ::std::weak_ptr< ViewEventHandler > ViewEventHandlerWeakPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_VIEWEVENTHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/viewlayer.hxx b/slideshow/source/inc/viewlayer.hxx new file mode 100644 index 000000000..4fe8aa4e0 --- /dev/null +++ b/slideshow/source/inc/viewlayer.hxx @@ -0,0 +1,173 @@ +/* -*- 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_INC_VIEWLAYER_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_VIEWLAYER_HXX + +#include <sal/config.h> +#include <memory> +#include <com/sun/star/geometry/IntegerSize2D.hpp> + +namespace basegfx +{ + class B1DRange; + class B2DRange; + class B2DVector; + class B2DHomMatrix; + class B2DPolyPolygon; + typedef B2DVector B2DSize; +} +namespace cppcanvas +{ + class Canvas; + class CustomSprite; + typedef std::shared_ptr< Canvas > CanvasSharedPtr; + typedef std::shared_ptr< ::cppcanvas::CustomSprite > CustomSpriteSharedPtr; +} + + +/* Definition of ViewLayer interface */ + +namespace slideshow::internal + { + class View; + typedef std::shared_ptr< View > ViewSharedPtr; + + class ViewLayer + { + public: + virtual ~ViewLayer() {} + + /** Query whether layer displays on given view. + + @return true, if this layer displays on the given + view. + */ + virtual bool isOnView(ViewSharedPtr const& rView) const = 0; + + /** Get the associated canvas of this layer. + + The canvas returned by this method must not change, as + long as this object is alive. + */ + virtual cppcanvas::CanvasSharedPtr getCanvas() const = 0; + + /** Clear the clipped view layer area + + This method clears the area inside the clip polygon, + if none is set, the transformed unit rectangle of the + view. + */ + virtual void clear() const = 0; + + /** Clear the complete view + + This method clears the full view area (not only the + transformed unit rectangle, or within the clip). If + this ViewLayer represents the background layer, the + whole XSlideShowView is cleared. If this ViewLayer is + implemented using sprites (i.e. one of the upper + layers), the sprite is cleared to fully transparent. + */ + virtual void clearAll() const = 0; + + /** Create a sprite for this layer + + @param rSpriteSizePixel + Sprite size in device pixel + + @param nPriority + Sprite priority. This value determines the priority of + this sprite, relative to all other sprites of this + ViewLayer. The higher the priority, the closer to the + foreground the sprite will be. + + @return the sprite, or NULL on failure (or if this + canvas does not support sprites). + */ + virtual cppcanvas::CustomSpriteSharedPtr + createSprite( const basegfx::B2DSize& rSpriteSizePixel, + double nPriority ) const = 0; + + /** Set the layer priority range + + This method influences the relative priority of this + layer, i.e. the z position in relation to other layers + on the parent view. The higher the priority range, the + further in front the layer resides. + + @param rRange + Priority range, must be in the range [0,1] + */ + virtual void setPriority( const basegfx::B1DRange& rRange ) = 0; + + /** Get the overall view transformation. + + This method should <em>not</em> simply return the + underlying canvas' transformation, but rather provide + a layer above that. This enables clients of the + slideshow to set their own user space transformation + at the canvas, whilst the slideshow adds their + transformation on top of that. Concretely, this method + returns the user transform (implicitly calculated + from the setViewSize() method), combined with the view + transformation. + */ + virtual basegfx::B2DHomMatrix getTransformation() const = 0; + + virtual css::geometry::IntegerSize2D getTranslationOffset() const = 0; + + /** Get the overall view transformation. + + Same transformation as with getTransformation(), only + that you can safely use this one to position sprites + on screen (no ViewLayer offsets included whatsoever). + */ + virtual basegfx::B2DHomMatrix getSpriteTransformation() const = 0; + + /** Set clipping on this view layer. + + @param rClip + Clip poly-polygon to set. The polygon is interpreted + in the user coordinate system, i.e. the view layer has + the size as given by setViewSize() on its + corresponding View. + */ + virtual void setClip( const basegfx::B2DPolyPolygon& rClip ) = 0; + + /** Resize this view layer. + + @param rArea + New area to cover. The area is interpreted in the user + coordinate system, i.e. relative to the size as given + by setViewSize() on the corresponding View. + + @return true, if layer was actually resized (which + invalidates its content) + */ + virtual bool resize( const basegfx::B2DRange& rArea ) = 0; + + }; + + typedef std::shared_ptr< ViewLayer > ViewLayerSharedPtr; +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_VIEWLAYER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/viewupdate.hxx b/slideshow/source/inc/viewupdate.hxx new file mode 100644 index 000000000..a5e2ee340 --- /dev/null +++ b/slideshow/source/inc/viewupdate.hxx @@ -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 . + */ + +#ifndef INCLUDED_SLIDESHOW_SOURCE_INC_VIEWUPDATE_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_VIEWUPDATE_HXX + +#include "disposable.hxx" +#include <memory> + +/* Definition of ViewUpdate interface */ + +namespace slideshow::internal + { + /** Interface for something that can perform view updates. + + Use this interface for objects that perform view updates + from time to time, e.g. slide content. Availability of + updates can be queried, and subsequently performed. + */ + class ViewUpdate : public Disposable + { + public: + /** Perform the update action on all views + + @return true, if the update was performed + successfully, false otherwise. + */ + virtual bool update() = 0; + + /** Query whether updates are pending + + @return true, if updates are pending. Calling update() + subsequently will perform the pending update then. + */ + virtual bool needsUpdate() const = 0; + }; + + typedef ::std::shared_ptr< ViewUpdate > ViewUpdateSharedPtr; + +} + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_VIEWUPDATE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/inc/wakeupevent.hxx b/slideshow/source/inc/wakeupevent.hxx new file mode 100644 index 000000000..8a1379f02 --- /dev/null +++ b/slideshow/source/inc/wakeupevent.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_INC_WAKEUPEVENT_HXX +#define INCLUDED_SLIDESHOW_SOURCE_INC_WAKEUPEVENT_HXX + +#include <canvas/elapsedtime.hxx> + +#include "event.hxx" +#include "activitiesqueue.hxx" + +namespace slideshow::internal { + +/** Little helper class, used to set Activities active again + after some sleep period. + + Clients can use this class to schedule wakeup events at + the EventQueue, to avoid busy-waiting for the next + discrete time instant. +*/ +class WakeupEvent : public Event +{ +public: + WakeupEvent( + std::shared_ptr< ::canvas::tools::ElapsedTime > const& pTimeBase, + ActivitiesQueue & rActivityQueue ); + WakeupEvent(const WakeupEvent&) = delete; + WakeupEvent& operator=(const WakeupEvent&) = delete; + + virtual void dispose() override; + virtual bool fire() override; + virtual bool isCharged() const override; + virtual double getActivationTime( double nCurrentTime ) const override; + + /// Start the internal timer + void start(); + + /** 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 ); + + /** Set activity to wakeup. + + The activity given here will be reinserted into the + ActivitiesQueue, once the timeout is reached. + */ + void setActivity( const ActivitySharedPtr& rActivity ); + +private: + ::canvas::tools::ElapsedTime maTimer; + double mnNextTime; + ActivitySharedPtr mpActivity; + ActivitiesQueue& mrActivityQueue; +}; + +typedef ::std::shared_ptr< WakeupEvent > WakeupEventSharedPtr; + +} // namespace presentation::internal + +#endif // INCLUDED_SLIDESHOW_SOURCE_INC_WAKEUPEVENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |