1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <sal/log.hxx>
22 #include <com/sun/star/presentation/ParagraphTarget.hpp>
23 #include <com/sun/star/animations/Timing.hpp>
24 #include <com/sun/star/animations/AnimationAdditiveMode.hpp>
25 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
27 #include "nodetools.hxx"
28 #include <doctreenode.hxx>
29 #include "animationbasenode.hxx"
30 #include <delayevent.hxx>
31 #include <framerate.hxx>
36 using namespace com::sun::star
;
38 namespace slideshow::internal
{
40 AnimationBaseNode::AnimationBaseNode(
41 const uno::Reference
< animations::XAnimationNode
>& xNode
,
42 const BaseContainerNodeSharedPtr
& rParent
,
43 const NodeContext
& rContext
)
44 : BaseNode( xNode
, rParent
, rContext
),
45 mxAnimateNode( xNode
, uno::UNO_QUERY_THROW
),
46 maAttributeLayerHolder(),
47 maSlideSize( rContext
.maSlideSize
),
50 mpSubsetManager(rContext
.maContext
.mpSubsettableShapeManager
),
51 mbPreservedVisibility(true),
52 mbIsIndependentSubset( rContext
.mbIsIndependentSubset
),
55 // extract native node targets
56 // ===========================
59 uno::Reference
< drawing::XShape
> xShape( mxAnimateNode
->getTarget(),
62 // distinguish 5 cases:
64 // - plain shape target
65 // (NodeContext.mpMasterShapeSubset full set)
67 // - parent-generated subset (generate an
68 // independent subset)
70 // - parent-generated subset from iteration
71 // (generate a dependent subset)
73 // - XShape target at the XAnimatioNode (generate
74 // a plain shape target)
76 // - ParagraphTarget target at the XAnimationNode
77 // (generate an independent shape subset)
78 if( rContext
.mpMasterShapeSubset
)
80 if( rContext
.mpMasterShapeSubset
->isFullSet() )
82 // case 1: plain shape target from parent
83 mpShape
= rContext
.mpMasterShapeSubset
->getSubsetShape();
87 // cases 2 & 3: subset shape
88 mpShapeSubset
= rContext
.mpMasterShapeSubset
;
93 // no parent-provided shape, try to extract
94 // from XAnimationNode - cases 4 and 5
98 mpShape
= lookupAttributableShape( getContext().mpSubsettableShapeManager
,
103 // no shape provided. Maybe a ParagraphTarget?
104 presentation::ParagraphTarget aTarget
;
106 if( !(mxAnimateNode
->getTarget() >>= aTarget
) )
108 false, "could not extract any target information" );
110 xShape
= aTarget
.Shape
;
112 ENSURE_OR_THROW( xShape
.is(), "invalid shape in ParagraphTarget" );
114 mpShape
= lookupAttributableShape( getContext().mpSubsettableShapeManager
,
117 // NOTE: For shapes with ParagraphTarget, we ignore
118 // the SubItem property. We implicitly assume that it
119 // is set to ONLY_TEXT.
121 mxAnimateNode
->getSubItem() ==
122 presentation::ShapeAnimationSubType::ONLY_TEXT
||
123 mxAnimateNode
->getSubItem() ==
124 presentation::ShapeAnimationSubType::AS_WHOLE
,
125 "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? "
126 "Make up your mind, I'll ignore the subitem." );
128 // okay, found a ParagraphTarget with a valid XShape. Does the shape
129 // provide the given paragraph?
130 if( aTarget
.Paragraph
>= 0 &&
131 mpShape
->getTreeNodeSupplier().getNumberOfTreeNodes(
132 DocTreeNode::NodeType::LogicalParagraph
) > aTarget
.Paragraph
)
134 const DocTreeNode
aTreeNode(
135 mpShape
->getTreeNodeSupplier().getTreeNode(
137 DocTreeNode::NodeType::LogicalParagraph
) );
139 // CAUTION: the creation of the subset shape
140 // _must_ stay in the node constructor, since
141 // Slide::prefetchShow() initializes shape
142 // attributes right after animation import (or
143 // the Slide class must be changed).
145 std::make_shared
<ShapeSubset
>( mpShape
,
149 // Override NodeContext, and flag this node as
150 // a special independent subset one. This is
151 // important when applying initial attributes:
152 // independent shape subsets must be setup
153 // when the slide starts, since they, as their
154 // name suggest, can have state independent to
155 // the master shape. The following example
156 // might illustrate that: a master shape has
157 // no effect, one of the text paragraphs
158 // within it has an appear effect. Now, the
159 // respective paragraph must be invisible when
160 // the slide is initially shown, and become
161 // visible only when the effect starts.
162 mbIsIndependentSubset
= true;
164 // already enable subset right here, the
165 // setup of initial shape attributes of
166 // course needs the subset shape
167 // generated, to apply e.g. visibility
169 mpShapeSubset
->enableSubsetShape();
175 void AnimationBaseNode::dispose()
178 mpActivity
->dispose();
182 maAttributeLayerHolder
.reset();
183 mxAnimateNode
.clear();
185 mpShapeSubset
.reset();
190 bool AnimationBaseNode::init_st()
192 // if we've still got an old activity lying around, dispose it:
194 mpActivity
->dispose();
198 // note: actually disposing the activity too early might cause problems,
199 // because on dequeued() it calls endAnimation(pAnim->end()), thus ending
200 // animation _after_ last screen update.
201 // review that end() is properly called (which calls endAnimation(), too).
204 // TODO(F2): For restart functionality, we must regenerate activities,
205 // since they are not able to reset their state (or implement _that_)
206 mpActivity
= createActivity();
208 catch (uno::Exception
const&) {
209 TOOLS_WARN_EXCEPTION( "slideshow", "" );
210 // catch and ignore. We later handle empty activities, but for
211 // other nodes to function properly, the core functionality of
212 // this node must remain up and running.
217 bool AnimationBaseNode::resolve_st()
219 // enable shape subset for automatically generated
220 // subsets. Independent subsets are already setup
221 // during construction time. Doing it only here
222 // saves us a lot of sprites and shapes lying
223 // around. This is especially important for
224 // character-wise iterations, since the shape
225 // content (e.g. thousands of characters) would
226 // otherwise be painted character-by-character.
227 if (isDependentSubsettedShape() && mpShapeSubset
) {
228 mpShapeSubset
->enableSubsetShape();
233 void AnimationBaseNode::activate_st()
235 AttributableShapeSharedPtr
const pShape(getShape());
236 mbPreservedVisibility
= pShape
->isVisible();
238 // create new attribute layer
239 maAttributeLayerHolder
.createAttributeLayer(pShape
);
241 ENSURE_OR_THROW( maAttributeLayerHolder
.get(),
242 "Could not generate shape attribute layer" );
244 // TODO(Q2): This affects the way mpActivity
245 // works, but is performed here because of
246 // locality (we're fiddling with the additive mode
247 // here, anyway, and it's the only place where we
248 // do). OTOH, maybe the complete additive mode
249 // setup should be moved to the activities.
251 // for simple by-animations, the SMIL spec
252 // requires us to emulate "0,by-value" value list
253 // behaviour, with additive mode forced to "sum",
254 // no matter what the input is
255 // (http://www.w3.org/TR/smil20/animation.html#adef-by).
256 if( mxAnimateNode
->getBy().hasValue() &&
257 !mxAnimateNode
->getTo().hasValue() &&
258 !mxAnimateNode
->getFrom().hasValue() )
260 // force attribute mode to REPLACE (note the
261 // subtle discrepancy to the paragraph above,
262 // where SMIL requires SUM. This is internally
263 // handled by the FromToByActivity, and is
264 // because otherwise DOM values would not be
265 // handled correctly: the activity cannot
266 // determine whether an
267 // Activity::getUnderlyingValue() yields the
268 // DOM value, or already a summed-up conglomerate)
270 // Note that this poses problems with our
271 // hybrid activity duration (time or min number of frames),
272 // since if activities
273 // exceed their duration, wrong 'by' start
274 // values might arise ('Laser effect')
275 maAttributeLayerHolder
.get()->setAdditiveMode(
276 animations::AnimationAdditiveMode::REPLACE
);
280 // apply additive mode to newly created Attribute layer
281 maAttributeLayerHolder
.get()->setAdditiveMode(
282 mxAnimateNode
->getAdditive() );
285 // fake normal animation behaviour, even if we
286 // show nothing. This is the appropriate way to
287 // handle errors on Activity generation, because
288 // maybe all other effects on the slide are
289 // correctly initialized (but won't run, if we
290 // signal an error here)
292 // supply Activity (and the underlying Animation) with
293 // it's AttributeLayer, to perform the animation on
294 mpActivity
->setTargets( getShape(), maAttributeLayerHolder
.get() );
296 // add to activities queue
300 // Actually, DO generate the event for empty activity,
301 // to keep the chain of animations running
302 BaseNode::scheduleDeactivationEvent();
306 void AnimationBaseNode::deactivate_st( NodeState eDestState
)
308 if (eDestState
== FROZEN
&& mpActivity
)
311 if (isDependentSubsettedShape()) {
312 // for dependent subsets, remove subset shape
313 // from layer, re-integrate subsetted part
314 // back into original shape. For independent
315 // subsets, we cannot make any assumptions
316 // about subset attribute state relative to
317 // master shape, thus, have to keep it. This
318 // will effectively re-integrate the subsetted
319 // part into the original shape (whose
320 // animation will hopefully have ended, too)
322 // this statement will save a whole lot of
323 // sprites for iterated text effects, since
324 // those sprites will only exist during the
325 // actual lifetime of the effects
327 mpShapeSubset
->disableSubsetShape();
331 if (eDestState
!= ENDED
)
334 // no shape anymore, no layer needed:
335 maAttributeLayerHolder
.reset();
337 if (! isDependentSubsettedShape()) {
339 // for all other shapes, removing the
340 // attribute layer quite possibly changes
341 // shape display. Thus, force update
342 AttributableShapeSharedPtr
const pShape( getShape() );
344 // don't anybody dare to check against
345 // pShape->isVisible() here, removing the
346 // attribute layer might actually make the
348 getContext().mpSubsettableShapeManager
->notifyShapeUpdate( pShape
);
352 // kill activity, if still running
353 mpActivity
->dispose();
358 void AnimationBaseNode::removeEffect()
360 if (!isDependentSubsettedShape()) {
361 AttributableShapeSharedPtr
const pShape(getShape());
362 pShape
->setVisibility(!mbPreservedVisibility
);
363 getContext().mpSubsettableShapeManager
->notifyShapeUpdate( pShape
);
364 pShape
->setVisibility(mbPreservedVisibility
);
368 bool AnimationBaseNode::hasPendingAnimation() const
370 // TODO(F1): This might not always be true. Are there 'inactive'
375 bool AnimationBaseNode::enqueueActivity() const
377 return getContext().mrActivitiesQueue
.addActivity( mpActivity
);
380 #if defined(DBG_UTIL)
381 void AnimationBaseNode::showState() const
383 BaseNode::showState();
385 SAL_INFO( "slideshow.verbose", "AnimationBaseNode info: independent subset=" <<
386 (mbIsIndependentSubset
? "y" : "n") );
390 ActivitiesFactory::CommonParameters
391 AnimationBaseNode::fillCommonParameters() const
393 double nDuration
= 0.0;
395 // TODO(F3): Duration/End handling is barely there
396 if( !(mxAnimateNode
->getDuration() >>= nDuration
) ) {
397 mxAnimateNode
->getEnd() >>= nDuration
; // Wah.
400 // minimal duration we fallback to (avoid 0 here!)
401 nDuration
= ::std::max( 0.001, nDuration
);
403 const bool bAutoReverse( mxAnimateNode
->getAutoReverse() );
405 std::optional
<double> aRepeats
;
407 if( mxAnimateNode
->getRepeatCount() >>= nRepeats
) {
411 if( mxAnimateNode
->getRepeatDuration() >>= nRepeats
) {
412 // when repeatDuration is given,
413 // autoreverse does _not_ modify the
414 // active duration. Thus, calc repeat
415 // count with already adapted simple
416 // duration (twice the specified duration)
418 // convert duration back to repeat counts
420 aRepeats
= nRepeats
/ (2.0 * nDuration
);
422 aRepeats
= nRepeats
/ nDuration
;
426 // no double value for both values - Timing::INDEFINITE?
427 animations::Timing eTiming
;
429 if( !(mxAnimateNode
->getRepeatDuration() >>= eTiming
) ||
430 eTiming
!= animations::Timing_INDEFINITE
)
432 if( !(mxAnimateNode
->getRepeatCount() >>= eTiming
) ||
433 eTiming
!= animations::Timing_INDEFINITE
)
435 // no indefinite timing, no other values given -
436 // use simple run, i.e. repeat of 1.0
444 double nAcceleration
= 0.0;
445 double nDeceleration
= 0.0;
446 BaseNodeSharedPtr
const pSelf( getSelf() );
447 for ( std::shared_ptr
<BaseNode
> pNode( pSelf
);
448 pNode
; pNode
= pNode
->getParentNode() )
450 uno::Reference
<animations::XAnimationNode
> const xAnimationNode(
451 pNode
->getXAnimationNode() );
452 nAcceleration
= std::max( nAcceleration
,
453 xAnimationNode
->getAcceleration() );
454 nDeceleration
= std::max( nDeceleration
,
455 xAnimationNode
->getDecelerate() );
458 EventSharedPtr pEndEvent
;
460 pEndEvent
= makeEvent( [pSelf
] () {pSelf
->deactivate(); },
461 u
"AnimationBaseNode::deactivate"_ustr
);
464 // Calculate the minimum frame count that depends on the duration and
465 // the minimum frame count.
466 const sal_Int32
nMinFrameCount (std::clamp
<sal_Int32
>(
467 basegfx::fround(nDuration
* FrameRate::MinimumFramesPerSecond
), 1, 10));
469 return ActivitiesFactory::CommonParameters(
470 std::move(pEndEvent
),
471 getContext().mrEventQueue
,
472 getContext().mrActivitiesQueue
,
483 AttributableShapeSharedPtr
const & AnimationBaseNode::getShape() const
485 // any subsetting at all?
487 return mpShapeSubset
->getSubsetShape();
489 return mpShape
; // nope, plain shape always
492 } // namespace slideshow
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */