diff options
author | Kurt Zenker <kz@openoffice.org> | 2005-11-02 11:55:15 +0000 |
---|---|---|
committer | Kurt Zenker <kz@openoffice.org> | 2005-11-02 11:55:15 +0000 |
commit | 5b833dc4c79ee8048e2f3791260074e2ffad63b6 (patch) | |
tree | 3ddf6011a2ecb5e236b2eaecf5f6f033374f2441 /canvas/source/tools | |
parent | 2819e9eb89c99e7c462e7b0feead7db96150c9ee (diff) |
INTEGRATION: CWS canvas02 (1.1.2); FILE ADDED
2005/10/28 12:52:14 thb 1.1.2.6: #i48939# Relaxed debug postcondition checking
2005/10/27 13:43:57 thb 1.1.2.5: #i48939# Filtering the list of updatable sprites from NULL sprites
2005/10/18 08:30:31 thb 1.1.2.4: #i10000# gcc does not like old-style structs types prefixed by const when declaring a variable.
2005/10/14 21:40:10 thb 1.1.2.3: #118732# Added working sprite prio; now correctly calculating remaining (inactive) set of sprites.
2005/10/11 15:41:19 thb 1.1.2.2: #i54170# Corrected license headers
2005/06/17 23:41:36 thb 1.1.2.1: #i48939# Implementations for the new canvas base classes
Diffstat (limited to 'canvas/source/tools')
-rw-r--r-- | canvas/source/tools/spriteredrawmanager.cxx | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/canvas/source/tools/spriteredrawmanager.cxx b/canvas/source/tools/spriteredrawmanager.cxx new file mode 100644 index 000000000000..eb09ad654216 --- /dev/null +++ b/canvas/source/tools/spriteredrawmanager.cxx @@ -0,0 +1,498 @@ +/************************************************************************* + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: spriteredrawmanager.cxx,v $ + * + * $Revision: 1.2 $ + * + * last change: $Author: kz $ $Date: 2005-11-02 12:55:15 $ + * + * The Contents of this file are made available subject to + * the terms of GNU Lesser General Public License Version 2.1. + * + * + * GNU Lesser General Public License Version 2.1 + * ============================================= + * Copyright 2005 by Sun Microsystems, Inc. + * 901 San Antonio Road, Palo Alto, CA 94303, USA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * This library 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 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ************************************************************************/ + +#include <canvas/debug.hxx> +#include <canvas/spriteredrawmanager.hxx> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/tools/canvastools.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/range/rangeexpander.hxx> + +#include <algorithm> +#include <functional> +#include <boost/bind.hpp> + + +namespace canvas +{ + namespace + { + /** Helper class to condense sprite updates into a single action + + This class tracks the sprite changes over the recorded + change list, and generates a single update action from + that (note that per screen update, several moves, + visibility changes and content updates might happen) + */ + class SpriteTracer + { + public: + SpriteTracer( const Sprite::Reference& rAffectedSprite ) : + mpAffectedSprite(rAffectedSprite), + maMoveStartArea(), + maMoveEndArea(), + mbIsMove( false ), + mbIsGenericUpdate( false ) + { + } + + void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord ) + { + // only deal with change events from the currently + // affected sprite + if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite ) + { + switch( rSpriteRecord.meChangeType ) + { + case SpriteRedrawManager::SpriteChangeRecord::move: + if( !mbIsMove ) + { + // no move yet - this must be the first one + maMoveStartArea = ::basegfx::B2DRectangle( + rSpriteRecord.maOldPos, + rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() ); + mbIsMove = true; + } + + maMoveEndArea = rSpriteRecord.maUpdateArea; + break; + + case SpriteRedrawManager::SpriteChangeRecord::update: + // update end update area of the + // sprite. Thus, every update() action + // _after_ the last move will correctly + // update the final repaint area. And this + // does not interfere with subsequent + // moves, because moves always perform a + // hard set of maMoveEndArea to their + // stored value + maMoveEndArea.expand( rSpriteRecord.maUpdateArea ); + mbIsGenericUpdate = true; + break; + + default: + ENSURE_AND_THROW( false, + "Unexpected case in SpriteUpdater::operator()" ); + break; + } + } + } + + void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const + { + if( mbIsMove ) + { + if( !maMoveStartArea.isEmpty() || + !maMoveEndArea.isEmpty() ) + { + // if mbIsGenericUpdate is false, this is a + // pure move (i.e. no other update + // operations). Pass that information on to + // the SpriteInfo + const bool bIsPureMove( !mbIsGenericUpdate ); + + // ignore the case that start and end update + // area overlap - the b2dconnectedranges + // handle that, anyway. doing it this way + // ensures that we have both old and new area + // stored + + // round all given range up to enclosing + // integer rectangle - since the whole thing + // here is about + + // first, draw the new sprite position + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), + SpriteRedrawManager::SpriteInfo( + mpAffectedSprite, + maMoveEndArea, + true, + bIsPureMove ) ); + + // then, clear the old place (looks smoother + // this way) + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ), + SpriteRedrawManager::SpriteInfo( + Sprite::Reference(), + maMoveStartArea, + true, + bIsPureMove ) ); + } + } + else if( mbIsGenericUpdate && + !maMoveEndArea.isEmpty() ) + { + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), + SpriteRedrawManager::SpriteInfo( + mpAffectedSprite, + maMoveEndArea, + true ) ); + } + } + + private: + Sprite::Reference mpAffectedSprite; + ::basegfx::B2DRectangle maMoveStartArea; + ::basegfx::B2DRectangle maMoveEndArea; + + /// True, if at least one move was encountered + bool mbIsMove; + + /// True, if at least one generic update was encountered + bool mbIsGenericUpdate; + }; + + + /** SpriteChecker functor, which for every sprite checks the + given update vector for necessary screen updates + */ + class SpriteUpdater + { + public: + /** Generate update area list + + @param rUpdater + Reference to an updater object, which will receive the + update areas. + + @param rChangeContainer + Container with all sprite change requests + + */ + SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater, + const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) : + mrUpdater( rUpdater ), + mrChangeContainer( rChangeContainer ) + { + } + + /** Call this method for every sprite on your screen + + This method scans the change container, collecting all + update info for the given sprite into one or two + update operations, which in turn are inserted into the + connected ranges processor. + + @param rSprite + Current sprite to collect update info for. + */ + void operator()( const Sprite::Reference& rSprite ) + { + const SpriteTracer aSpriteTracer( + ::std::for_each( mrChangeContainer.begin(), + mrChangeContainer.end(), + SpriteTracer( rSprite ) ) ); + + aSpriteTracer.commit( mrUpdater ); + } + + private: + SpriteRedrawManager::SpriteConnectedRanges& mrUpdater; + const SpriteRedrawManager::VectorOfChangeRecords& mrChangeContainer; + }; + } + + void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const + { + // TODO(T3): This is NOT thread safe at all. This only works + // under the assumption that NOBODY changes ANYTHING + // concurrently, while this method is on the stack. We should + // really rework the canvas::Sprite interface, in such a way + // that it dumps ALL its state with a single, atomic + // call. Then, we store that state locally. This prolly goes + // in line with the problem of having sprite state available + // for the frame before the last frame; plus, it avoids + // frequent locks of the object mutices + SpriteComparator aSpriteComparator; + + // sort sprites after prio + VectorOfSprites aSortedSpriteVector; + ::std::copy( maSprites.begin(), + maSprites.end(), + ::std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) ); + ::std::sort( aSortedSpriteVector.begin(), + aSortedSpriteVector.end(), + aSpriteComparator ); + + // extract all referenced sprites from the maChangeRecords + // (copy sprites, make the list unique, regarding the + // sprite pointer). This assumes that, until this scope + // ends, nobody changes the maChangeRecords vector! + VectorOfSprites aUpdatableSprites; + VectorOfChangeRecords::const_iterator aCurrRecord( maChangeRecords.begin() ); + const VectorOfChangeRecords::const_iterator aEndRecords( maChangeRecords.end() ); + while( aCurrRecord != aEndRecords ) + { + const Sprite::Reference& rSprite( aCurrRecord->getSprite() ); + if( rSprite.is() ) + aUpdatableSprites.push_back( rSprite ); + ++aCurrRecord; + } + + VectorOfSprites::iterator aBegin( aUpdatableSprites.begin() ); + VectorOfSprites::iterator aEnd ( aUpdatableSprites.end() ); + ::std::sort( aBegin, + aEnd, + aSpriteComparator ); + + aEnd = ::std::unique( aBegin, aEnd ); + + // for each unique sprite, check the change event vector, + // calculate the update operation from that, and add the + // result to the aUpdateArea. + ::std::for_each( aBegin, + aEnd, + SpriteUpdater( rUpdateAreas, + maChangeRecords) ); + + // TODO(P2): Implement your own output iterator adapter, to + // avoid that totally superfluous temp aUnchangedSprites + // vector. + + // add all sprites to rUpdateAreas, that are _not_ already + // contained in the uniquified vector of changed ones + // (i.e. the difference between aSortedSpriteVector and + // aUpdatableSprites). + VectorOfSprites aUnchangedSprites; + ::std::set_difference( aSortedSpriteVector.begin(), + aSortedSpriteVector.end(), + aBegin, aEnd, + ::std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites) ); + + // add each remaining unchanged sprite to connected ranges, + // marked as "don't need update" + VectorOfSprites::const_iterator aCurr( aUnchangedSprites.begin() ); + const VectorOfSprites::const_iterator aEnd2( aUnchangedSprites.end() ); + while( aCurr != aEnd2 ) + { + const ::basegfx::B2DRange& rUpdateArea( (*aCurr)->getUpdateArea() ); + rUpdateAreas.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ), + SpriteInfo(*aCurr, + rUpdateArea, + false) ); + ++aCurr; + } + } + + bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& o_rMoveStart, + ::basegfx::B2DRectangle& o_rMoveEnd, + const UpdateArea& rUpdateArea, + ::std::size_t nNumSprites ) const + { + // check for a solitary move, which consists of exactly two + // pure-move entries, the first with valid, the second with + // invalid sprite (see SpriteTracer::commit()). Note that we + // cannot simply store some flag in SpriteTracer::commit() + // above and just check that here, since during the connected + // range calculations, other sprites might get merged into the + // same region (thus spoiling the scrolling move + // optimization). + if( nNumSprites != 2 ) + return false; + + const SpriteConnectedRanges::ComponentListType::const_iterator aFirst( + rUpdateArea.maComponentList.begin() ); + SpriteConnectedRanges::ComponentListType::const_iterator aSecond( + aFirst ); ++aSecond; + + if( !aFirst->second.isPureMove() || + !aSecond->second.isPureMove() || + !aFirst->second.getSprite().is() || + // use _true_ update area, not the rounded version + !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) || + aSecond->second.getSprite().is() ) + { + // either no move update, or incorrect sprite, or sprite + // content not fully opaque over update region. + return false; + } + + o_rMoveStart = aSecond->second.getUpdateArea(); + o_rMoveEnd = aFirst->second.getUpdateArea(); + +#if OSL_DEBUG_LEVEL > 0 + ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart ); + aTotalBounds.expand( o_rMoveEnd ); + + OSL_POSTCOND( rUpdateArea.maTotalBounds.equal( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( aTotalBounds )), + "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch" ); + OSL_POSTCOND( o_rMoveStart.getRange().equal( + o_rMoveEnd.getRange()), + "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size" ); +#endif + + return true; + } + + bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect, + const AreaComponent& rComponent ) const + { + const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() ); + + if( !pAffectedSprite.is() ) + return true; // no sprite, no opaque update! + + return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect ); + } + + bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea, + ::std::size_t nNumSprites ) const + { + // check whether the sprites in the update area's list will + // fully cover the given area _and_ to that in an opaque way + // (i.e. no alpha, no non-rectangular sprite content). + + // TODO(P1): Come up with a smarter early-exit criterion here + // (though, I think, the case that _lots_ of sprites _fully_ + // cover a rectangular area _without_ any holes is extremely + // improbable) + + // avoid checking large number of sprites (and probably fail, + // anyway). Note: the case nNumSprites < 1 should normally not + // happen, as handleArea() calls backgroundPaint() then. + if( nNumSprites > 3 || nNumSprites < 1 ) + return false; + + const SpriteConnectedRanges::ComponentListType::const_iterator aBegin( + rUpdateArea.maComponentList.begin() ); + const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( + rUpdateArea.maComponentList.end() ); + + // now, calc the _true_ update area, by merging all sprite's + // true update areas into one rectangle + ::basegfx::B2DRange aTrueArea( aBegin->second.getUpdateArea() ); + ::std::for_each( ++rUpdateArea.maComponentList.begin(), + aEnd, + ::boost::bind( ::basegfx::B2DRangeExpander(aTrueArea), + ::boost::bind( &SpriteInfo::getUpdateArea, + ::boost::bind( ::std::select2nd<AreaComponent>(), + _1 ) ) ) ); + + // and check whether _any_ of the sprites tells that its area + // update will not be opaque. + return (::std::find_if( aBegin, + aEnd, + ::boost::bind( &SpriteRedrawManager::isAreaUpdateNotOpaque, + this, + ::boost::cref(aTrueArea), + _1 ) ) == aEnd ); + } + + bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const + { + // check whether SpriteInfo::needsUpdate returns false for + // all elements of this area's contained sprites + // + // if not a single changed sprite found - just ignore this + // component (return false) + const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( + rUpdateArea.maComponentList.end() ); + return (::std::find_if( rUpdateArea.maComponentList.begin(), + aEnd, + ::boost::bind( &SpriteInfo::needsUpdate, + ::boost::bind( + ::std::select2nd<SpriteConnectedRanges::ComponentType>(), + _1 ) ) ) != aEnd ); + } + + SpriteRedrawManager::SpriteRedrawManager() : + maSprites(), + maChangeRecords() + { + } + + void SpriteRedrawManager::disposing() + { + // drop all references + maChangeRecords.clear(); + + // dispose all sprites - the spritecanvas, and by delegation, + // this object, is the owner of the sprites. After all, a + // sprite without a canvas to render into makes not terribly + // much sense. + + // TODO(Q3): Once boost 1.33 is in, change back to for_each + // with ::boost::mem_fn. For the time being, explicit loop due + // to cdecl declaration of all UNO methods. + ListOfSprites::reverse_iterator aCurr( maSprites.rbegin() ); + ListOfSprites::reverse_iterator aEnd( maSprites.rend() ); + while( aCurr != aEnd ) + (*aCurr++)->dispose(); + + maSprites.clear(); + } + + void SpriteRedrawManager::clearChangeRecords() + { + maChangeRecords.clear(); + } + + void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite ) + { + maSprites.push_back( rSprite ); + } + + void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite ) + { + maSprites.remove( rSprite ); + } + + void SpriteRedrawManager::moveSprite( const Sprite::Reference& rSprite, + const ::basegfx::B2DPoint& rOldPos, + const ::basegfx::B2DPoint& rNewPos, + const ::basegfx::B2DVector& rSpriteSize ) + { + maChangeRecords.push_back( SpriteChangeRecord( rSprite, + rOldPos, + rNewPos, + rSpriteSize ) ); + } + + void SpriteRedrawManager::updateSprite( const Sprite::Reference& rSprite, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rUpdateArea ) + { + maChangeRecords.push_back( SpriteChangeRecord( rSprite, + rPos, + rUpdateArea ) ); + } + +} |