summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/animationnodes/animationbasenode.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'slideshow/source/engine/animationnodes/animationbasenode.cxx')
-rw-r--r--slideshow/source/engine/animationnodes/animationbasenode.cxx494
1 files changed, 494 insertions, 0 deletions
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: */