Branch libreoffice-5-0-4
[LibreOffice.git] / canvas / source / tools / spriteredrawmanager.cxx
bloba98a65e261c1cffca11733805eb569b312c07a0d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <canvas/debug.hxx>
22 #include <tools/diagnose_ex.h>
23 #include <canvas/spriteredrawmanager.hxx>
25 #include <basegfx/range/b2drectangle.hxx>
26 #include <basegfx/tools/canvastools.hxx>
27 #include <basegfx/vector/b2dsize.hxx>
29 #include <algorithm>
30 #include <o3tl/compat_functional.hxx>
31 #include <boost/bind.hpp>
34 namespace canvas
36 namespace
38 /** Helper class to condense sprite updates into a single action
40 This class tracks the sprite changes over the recorded
41 change list, and generates a single update action from
42 that (note that per screen update, several moves,
43 visibility changes and content updates might happen)
45 class SpriteTracer
47 public:
48 SpriteTracer( const Sprite::Reference& rAffectedSprite ) :
49 mpAffectedSprite(rAffectedSprite),
50 maMoveStartArea(),
51 maMoveEndArea(),
52 mbIsMove( false ),
53 mbIsGenericUpdate( false )
57 void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
59 // only deal with change events from the currently
60 // affected sprite
61 if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite )
63 switch( rSpriteRecord.meChangeType )
65 case SpriteRedrawManager::SpriteChangeRecord::move:
66 if( !mbIsMove )
68 // no move yet - this must be the first one
69 maMoveStartArea = ::basegfx::B2DRectangle(
70 rSpriteRecord.maOldPos,
71 rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
72 mbIsMove = true;
75 maMoveEndArea = rSpriteRecord.maUpdateArea;
76 break;
78 case SpriteRedrawManager::SpriteChangeRecord::update:
79 // update end update area of the
80 // sprite. Thus, every update() action
81 // _after_ the last move will correctly
82 // update the final repaint area. And this
83 // does not interfere with subsequent
84 // moves, because moves always perform a
85 // hard set of maMoveEndArea to their
86 // stored value
87 maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
88 mbIsGenericUpdate = true;
89 break;
91 default:
92 ENSURE_OR_THROW( false,
93 "Unexpected case in SpriteUpdater::operator()" );
94 break;
99 void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
101 if( mbIsMove )
103 if( !maMoveStartArea.isEmpty() ||
104 !maMoveEndArea.isEmpty() )
106 // if mbIsGenericUpdate is false, this is a
107 // pure move (i.e. no other update
108 // operations). Pass that information on to
109 // the SpriteInfo
110 const bool bIsPureMove( !mbIsGenericUpdate );
112 // ignore the case that start and end update
113 // area overlap - the b2dconnectedranges
114 // handle that, anyway. doing it this way
115 // ensures that we have both old and new area
116 // stored
118 // round all given range up to enclosing
119 // integer rectangle - since the whole thing
120 // here is about
122 // first, draw the new sprite position
123 rUpdateCollector.addRange(
124 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
125 SpriteRedrawManager::SpriteInfo(
126 mpAffectedSprite,
127 maMoveEndArea,
128 true,
129 bIsPureMove ) );
131 // then, clear the old place (looks smoother
132 // this way)
133 rUpdateCollector.addRange(
134 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
135 SpriteRedrawManager::SpriteInfo(
136 Sprite::Reference(),
137 maMoveStartArea,
138 true,
139 bIsPureMove ) );
142 else if( mbIsGenericUpdate &&
143 !maMoveEndArea.isEmpty() )
145 rUpdateCollector.addRange(
146 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
147 SpriteRedrawManager::SpriteInfo(
148 mpAffectedSprite,
149 maMoveEndArea,
150 true ) );
154 private:
155 Sprite::Reference mpAffectedSprite;
156 ::basegfx::B2DRectangle maMoveStartArea;
157 ::basegfx::B2DRectangle maMoveEndArea;
159 /// True, if at least one move was encountered
160 bool mbIsMove;
162 /// True, if at least one generic update was encountered
163 bool mbIsGenericUpdate;
167 /** SpriteChecker functor, which for every sprite checks the
168 given update vector for necessary screen updates
170 class SpriteUpdater
172 public:
173 /** Generate update area list
175 @param rUpdater
176 Reference to an updater object, which will receive the
177 update areas.
179 @param rChangeContainer
180 Container with all sprite change requests
183 SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater,
184 const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) :
185 mrUpdater( rUpdater ),
186 mrChangeContainer( rChangeContainer )
190 /** Call this method for every sprite on your screen
192 This method scans the change container, collecting all
193 update info for the given sprite into one or two
194 update operations, which in turn are inserted into the
195 connected ranges processor.
197 @param rSprite
198 Current sprite to collect update info for.
200 void operator()( const Sprite::Reference& rSprite )
202 const SpriteTracer aSpriteTracer(
203 ::std::for_each( mrChangeContainer.begin(),
204 mrChangeContainer.end(),
205 SpriteTracer( rSprite ) ) );
207 aSpriteTracer.commit( mrUpdater );
210 private:
211 SpriteRedrawManager::SpriteConnectedRanges& mrUpdater;
212 const SpriteRedrawManager::VectorOfChangeRecords& mrChangeContainer;
216 void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const
218 // TODO(T3): This is NOT thread safe at all. This only works
219 // under the assumption that NOBODY changes ANYTHING
220 // concurrently, while this method is on the stack. We should
221 // really rework the canvas::Sprite interface, in such a way
222 // that it dumps ALL its state with a single, atomic
223 // call. Then, we store that state locally. This prolly goes
224 // in line with the problem of having sprite state available
225 // for the frame before the last frame; plus, it avoids
226 // frequent locks of the object mutices
227 SpriteWeakOrder aSpriteComparator;
229 // put all sprites that have changed content into update areas
230 ListOfSprites::const_iterator aCurrSprite( maSprites.begin() );
231 const ListOfSprites::const_iterator aEndSprite ( maSprites.end() );
232 while( aCurrSprite != aEndSprite )
234 if( (*aCurrSprite)->isContentChanged() )
235 const_cast<SpriteRedrawManager*>(this)->updateSprite( *aCurrSprite,
236 (*aCurrSprite)->getPosPixel(),
237 (*aCurrSprite)->getUpdateArea() );
238 ++aCurrSprite;
241 // sort sprites after prio
242 VectorOfSprites aSortedSpriteVector;
243 ::std::copy( maSprites.begin(),
244 maSprites.end(),
245 ::std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) );
246 ::std::sort( aSortedSpriteVector.begin(),
247 aSortedSpriteVector.end(),
248 aSpriteComparator );
250 // extract all referenced sprites from the maChangeRecords
251 // (copy sprites, make the list unique, regarding the
252 // sprite pointer). This assumes that, until this scope
253 // ends, nobody changes the maChangeRecords vector!
254 VectorOfSprites aUpdatableSprites;
255 VectorOfChangeRecords::const_iterator aCurrRecord( maChangeRecords.begin() );
256 const VectorOfChangeRecords::const_iterator aEndRecords( maChangeRecords.end() );
257 while( aCurrRecord != aEndRecords )
259 const Sprite::Reference& rSprite( aCurrRecord->getSprite() );
260 if( rSprite.is() )
261 aUpdatableSprites.push_back( rSprite );
262 ++aCurrRecord;
265 ::std::sort( aUpdatableSprites.begin(),
266 aUpdatableSprites.end(),
267 aSpriteComparator );
269 VectorOfSprites::iterator aEnd=
270 ::std::unique( aUpdatableSprites.begin(),
271 aUpdatableSprites.end() );
273 // for each unique sprite, check the change event vector,
274 // calculate the update operation from that, and add the
275 // result to the aUpdateArea.
276 ::std::for_each( aUpdatableSprites.begin(),
277 aEnd,
278 SpriteUpdater( rUpdateAreas,
279 maChangeRecords) );
281 // TODO(P2): Implement your own output iterator adapter, to
282 // avoid that totally superfluous temp aUnchangedSprites
283 // vector.
285 // add all sprites to rUpdateAreas, that are _not_ already
286 // contained in the uniquified vector of changed ones
287 // (i.e. the difference between aSortedSpriteVector and
288 // aUpdatableSprites).
289 VectorOfSprites aUnchangedSprites;
290 ::std::set_difference( aSortedSpriteVector.begin(),
291 aSortedSpriteVector.end(),
292 aUpdatableSprites.begin(),
293 aEnd,
294 ::std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites),
295 aSpriteComparator );
297 // add each remaining unchanged sprite to connected ranges,
298 // marked as "don't need update"
299 VectorOfSprites::const_iterator aCurr( aUnchangedSprites.begin() );
300 const VectorOfSprites::const_iterator aEnd2( aUnchangedSprites.end() );
301 while( aCurr != aEnd2 )
303 const ::basegfx::B2DRange& rUpdateArea( (*aCurr)->getUpdateArea() );
304 rUpdateAreas.addRange(
305 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
306 SpriteInfo(*aCurr,
307 rUpdateArea,
308 false) );
309 ++aCurr;
313 #if OSL_DEBUG_LEVEL > 0
314 bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
316 return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
317 && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
318 && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
319 && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
322 bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
324 return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
325 && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
327 #endif
329 bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& o_rMoveStart,
330 ::basegfx::B2DRectangle& o_rMoveEnd,
331 const UpdateArea& rUpdateArea,
332 ::std::size_t nNumSprites ) const
334 // check for a solitary move, which consists of exactly two
335 // pure-move entries, the first with valid, the second with
336 // invalid sprite (see SpriteTracer::commit()). Note that we
337 // cannot simply store some flag in SpriteTracer::commit()
338 // above and just check that here, since during the connected
339 // range calculations, other sprites might get merged into the
340 // same region (thus spoiling the scrolling move
341 // optimization).
342 if( nNumSprites != 2 )
343 return false;
345 const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
346 rUpdateArea.maComponentList.begin() );
347 SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
348 aFirst ); ++aSecond;
350 if( !aFirst->second.isPureMove() ||
351 !aSecond->second.isPureMove() ||
352 !aFirst->second.getSprite().is() ||
353 // use _true_ update area, not the rounded version
354 !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
355 aSecond->second.getSprite().is() )
357 // either no move update, or incorrect sprite, or sprite
358 // content not fully opaque over update region.
359 return false;
362 o_rMoveStart = aSecond->second.getUpdateArea();
363 o_rMoveEnd = aFirst->second.getUpdateArea();
365 #if OSL_DEBUG_LEVEL > 0
366 ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
367 aTotalBounds.expand( o_rMoveEnd );
369 SAL_WARN_IF(!impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5),
370 "canvas",
371 "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
372 SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
373 "canvas",
374 "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
375 #endif
377 return true;
380 bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
381 const AreaComponent& rComponent ) const
383 const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
385 if( !pAffectedSprite.is() )
386 return true; // no sprite, no opaque update!
388 return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
391 bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea,
392 ::std::size_t nNumSprites ) const
394 // check whether the sprites in the update area's list will
395 // fully cover the given area _and_ do that in an opaque way
396 // (i.e. no alpha, no non-rectangular sprite content).
398 // TODO(P1): Come up with a smarter early-exit criterion here
399 // (though, I think, the case that _lots_ of sprites _fully_
400 // cover a rectangular area _without_ any holes is extremely
401 // improbable)
403 // avoid checking large number of sprites (and probably fail,
404 // anyway). Note: the case nNumSprites < 1 should normally not
405 // happen, as handleArea() calls backgroundPaint() then.
406 if( nNumSprites > 3 || nNumSprites < 1 )
407 return false;
409 // now, calc the _true_ update area, by merging all sprite's
410 // true update areas into one rectangle
411 ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() );
412 ::std::for_each( rUpdateArea.maComponentList.begin(),
413 rUpdateArea.maComponentList.end(),
414 ::boost::bind( (void (basegfx::B2DRange::*)(const basegfx::B2DRange&))(
415 &basegfx::B2DRange::expand),
416 aTrueArea,
417 ::boost::bind( &SpriteInfo::getUpdateArea,
418 ::boost::bind( ::o3tl::select2nd<AreaComponent>(),
419 _1 ) ) ) );
421 const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
422 rUpdateArea.maComponentList.end() );
424 // and check whether _any_ of the sprites tells that its area
425 // update will not be opaque.
426 return ::std::none_of( rUpdateArea.maComponentList.begin(),
427 aEnd,
428 ::boost::bind( &SpriteRedrawManager::isAreaUpdateNotOpaque,
429 this,
430 ::boost::cref(aTrueArea),
431 _1 ) );
434 bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
436 // check whether SpriteInfo::needsUpdate returns false for
437 // all elements of this area's contained sprites
439 // if not a single changed sprite found - just ignore this
440 // component (return false)
441 const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
442 rUpdateArea.maComponentList.end() );
443 return ::std::any_of( rUpdateArea.maComponentList.begin(),
444 aEnd,
445 ::boost::bind( &SpriteInfo::needsUpdate,
446 ::boost::bind(
447 ::o3tl::select2nd<SpriteConnectedRanges::ComponentType>(),
448 _1 ) ) );
451 SpriteRedrawManager::SpriteRedrawManager() :
452 maSprites(),
453 maChangeRecords()
457 void SpriteRedrawManager::disposing()
459 // drop all references
460 maChangeRecords.clear();
462 // dispose all sprites - the spritecanvas, and by delegation,
463 // this object, is the owner of the sprites. After all, a
464 // sprite without a canvas to render into makes not terribly
465 // much sense.
467 // TODO(Q3): Once boost 1.33 is in, change back to for_each
468 // with ::boost::mem_fn. For the time being, explicit loop due
469 // to cdecl declaration of all UNO methods.
470 ListOfSprites::reverse_iterator aCurr( maSprites.rbegin() );
471 ListOfSprites::reverse_iterator aEnd( maSprites.rend() );
472 while( aCurr != aEnd )
473 (*aCurr++)->dispose();
475 maSprites.clear();
478 void SpriteRedrawManager::clearChangeRecords()
480 maChangeRecords.clear();
483 void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite )
485 maSprites.push_back( rSprite );
488 void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite )
490 maSprites.remove( rSprite );
493 void SpriteRedrawManager::moveSprite( const Sprite::Reference& rSprite,
494 const ::basegfx::B2DPoint& rOldPos,
495 const ::basegfx::B2DPoint& rNewPos,
496 const ::basegfx::B2DVector& rSpriteSize )
498 maChangeRecords.push_back( SpriteChangeRecord( rSprite,
499 rOldPos,
500 rNewPos,
501 rSpriteSize ) );
504 void SpriteRedrawManager::updateSprite( const Sprite::Reference& rSprite,
505 const ::basegfx::B2DPoint& rPos,
506 const ::basegfx::B2DRange& rUpdateArea )
508 maChangeRecords.push_back( SpriteChangeRecord( rSprite,
509 rPos,
510 rUpdateArea ) );
515 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */