diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /slideshow/source/engine/animationnodes/animationnodefactory.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'slideshow/source/engine/animationnodes/animationnodefactory.cxx')
-rw-r--r-- | slideshow/source/engine/animationnodes/animationnodefactory.cxx | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/slideshow/source/engine/animationnodes/animationnodefactory.cxx b/slideshow/source/engine/animationnodes/animationnodefactory.cxx new file mode 100644 index 000000000..f9fa01b2f --- /dev/null +++ b/slideshow/source/engine/animationnodes/animationnodefactory.cxx @@ -0,0 +1,592 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "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::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: */ |