/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2017 CERN * @author Tomasz Wlostowski * @author Maciej Suminski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #ifdef __WXDEBUG__ #include #endif /* __WXDEBUG__ */ namespace KIGFX { class VIEW; class VIEW_ITEM_DATA { public: VIEW_ITEM_DATA() : m_view( nullptr ), m_flags( KIGFX::VISIBLE ), m_requiredUpdate( KIGFX::NONE ), m_drawPriority( 0 ), m_groups( nullptr ), m_groupsSize( 0 ) {} ~VIEW_ITEM_DATA() { deleteGroups(); } int getFlags() const { return m_flags; } private: friend class VIEW; /** * Function getLayers() * Returns layer numbers used by the item. * * @param aLayers[]: output layer index array * @param aCount: number of layer indices in aLayers[] */ void getLayers( int* aLayers, int& aCount ) const { int* layersPtr = aLayers; for( auto layer : m_layers ) *layersPtr++ = layer; aCount = m_layers.size(); } VIEW* m_view; ///< Current dynamic view the item is assigned to. int m_flags; ///< Visibility flags int m_requiredUpdate; ///< Flag required for updating int m_drawPriority; ///< Order to draw this item in a layer, lowest first ///> Helper for storing cached items group ids typedef std::pair GroupPair; ///> Indexes of cached GAL display lists corresponding to the item (for every layer it occupies). ///> (in the std::pair "first" stores layer number, "second" stores group id). GroupPair* m_groups; int m_groupsSize; /** * Function getGroup() * Returns number of the group id for the given layer, or -1 in case it was not cached before. * * @param aLayer is the layer number for which group id is queried. * @return group id or -1 in case there is no group id (ie. item is not cached). */ int getGroup( int aLayer ) const { for( int i = 0; i < m_groupsSize; ++i ) { if( m_groups[i].first == aLayer ) return m_groups[i].second; } return -1; } /** * Function getAllGroups() * Returns all group ids for the item (collected from all layers the item occupies). * * @return vector of group ids. */ std::vector getAllGroups() const { std::vector groups( m_groupsSize ); for( int i = 0; i < m_groupsSize; ++i ) { groups[i] = m_groups[i].second; } return groups; } /** * Function setGroup() * Sets a group id for the item and the layer combination. * * @param aLayer is the layer numbe. * @param aGroup is the group id. */ void setGroup( int aLayer, int aGroup ) { // Look if there is already an entry for the layer for( int i = 0; i < m_groupsSize; ++i ) { if( m_groups[i].first == aLayer ) { m_groups[i].second = aGroup; return; } } // If there was no entry for the given layer - create one GroupPair* newGroups = new GroupPair[m_groupsSize + 1]; if( m_groupsSize > 0 ) { std::copy( m_groups, m_groups + m_groupsSize, newGroups ); delete[] m_groups; } m_groups = newGroups; newGroups[m_groupsSize++] = GroupPair( aLayer, aGroup ); } /** * Function deleteGroups() * Removes all of the stored group ids. Forces recaching of the item. */ void deleteGroups() { delete[] m_groups; m_groups = nullptr; m_groupsSize = 0; } /** * Function storesGroups() * Returns information if the item uses at least one group id (ie. if it is cached at all). * * @returns true in case it is cached at least for one layer. */ inline bool storesGroups() const { return m_groupsSize > 0; } /** * Reorders the stored groups (to facilitate reordering of layers) * @see VIEW::ReorderLayerData * * @param aReorderMap is the mapping of old to new layer ids */ void reorderGroups( std::unordered_map aReorderMap ) { for( int i = 0; i < m_groupsSize; ++i ) { int orig_layer = m_groups[i].first; int new_layer = orig_layer; try { new_layer = aReorderMap.at( orig_layer ); } catch( const std::out_of_range& ) {} m_groups[i].first = new_layer; } } /// Stores layer numbers used by the item. std::vector m_layers; /** * Function saveLayers() * Saves layers used by the item. * * @param aLayers is an array containing layer numbers to be saved. * @param aCount is the size of the array. */ void saveLayers( int* aLayers, int aCount ) { m_layers.clear(); for( int i = 0; i < aCount; ++i ) { // this fires on some eagle board after EAGLE_PLUGIN::Load() wxASSERT( unsigned( aLayers[i] ) <= unsigned( VIEW::VIEW_MAX_LAYERS ) ); m_layers.push_back( aLayers[i] ); } } /** * Function viewRequiredUpdate() * Returns current update flag for an item. */ int requiredUpdate() const { return m_requiredUpdate; } /** * Function clearUpdateFlags() * Marks an item as already updated, so it is not going to be redrawn. */ void clearUpdateFlags() { m_requiredUpdate = NONE; } /** * Function isRenderable() * Returns if the item should be drawn or not. */ bool isRenderable() const { return m_flags == VISIBLE; } }; void VIEW::OnDestroy( VIEW_ITEM* aItem ) { auto data = aItem->viewPrivData(); if( !data ) return; if( data->m_view ) data->m_view->VIEW::Remove( aItem ); delete data; } VIEW::VIEW( bool aIsDynamic ) : m_enableOrderModifier( true ), m_scale( 4.0 ), m_minScale( 4.0 ), m_maxScale( 75000.0 ), m_mirrorX( false ), m_mirrorY( false ), m_painter( NULL ), m_gal( NULL ), m_dynamic( aIsDynamic ), m_useDrawPriority( false ), m_nextDrawPriority( 0 ), m_reverseDrawOrder( false ) { m_boundary.SetMaximum(); m_allItems.reserve( 32768 ); // Redraw everything at the beginning MarkDirty(); // View uses layers to display EDA_ITEMs (item may be displayed on several layers, for example // pad may be shown on pad, pad hole and solder paste layers). There are usual copper layers // (eg. F.Cu, B.Cu, internal and so on) and layers for displaying objects such as texts, // silkscreen, pads, vias, etc. for( int i = 0; i < VIEW_MAX_LAYERS; i++ ) AddLayer( i ); } VIEW::~VIEW() { for( LAYER_MAP::value_type& l : m_layers ) delete l.second.items; } void VIEW::AddLayer( int aLayer, bool aDisplayOnly ) { if( m_layers.find( aLayer ) == m_layers.end() ) { m_layers[aLayer] = VIEW_LAYER(); m_layers[aLayer].id = aLayer; m_layers[aLayer].items = new VIEW_RTREE(); m_layers[aLayer].renderingOrder = aLayer; m_layers[aLayer].visible = true; m_layers[aLayer].displayOnly = aDisplayOnly; m_layers[aLayer].target = TARGET_CACHED; } sortLayers(); } void VIEW::Add( VIEW_ITEM* aItem, int aDrawPriority ) { int layers[VIEW_MAX_LAYERS], layers_count; if( aDrawPriority < 0 ) aDrawPriority = m_nextDrawPriority++; if( !aItem->m_viewPrivData ) aItem->m_viewPrivData = new VIEW_ITEM_DATA; aItem->m_viewPrivData->m_view = this; aItem->m_viewPrivData->m_drawPriority = aDrawPriority; aItem->ViewGetLayers( layers, layers_count ); aItem->viewPrivData()->saveLayers( layers, layers_count ); m_allItems.push_back( aItem ); for( int i = 0; i < layers_count; ++i ) { VIEW_LAYER& l = m_layers[layers[i]]; l.items->Insert( aItem ); MarkTargetDirty( l.target ); } SetVisible( aItem, true ); Update( aItem, KIGFX::INITIAL_ADD ); } void VIEW::Remove( VIEW_ITEM* aItem ) { if( !aItem ) return; auto viewData = aItem->viewPrivData(); if( !viewData ) return; wxASSERT( viewData->m_view == this ); auto item = std::find( m_allItems.begin(), m_allItems.end(), aItem ); if( item != m_allItems.end() ) { m_allItems.erase( item ); viewData->clearUpdateFlags(); } int layers[VIEW::VIEW_MAX_LAYERS], layers_count; viewData->getLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { VIEW_LAYER& l = m_layers[layers[i]]; l.items->Remove( aItem ); MarkTargetDirty( l.target ); // Clear the GAL cache int prevGroup = viewData->getGroup( layers[i] ); if( prevGroup >= 0 ) m_gal->DeleteGroup( prevGroup ); } viewData->deleteGroups(); viewData->m_view = nullptr; } void VIEW::SetRequired( int aLayerId, int aRequiredId, bool aRequired ) { wxASSERT( (unsigned) aLayerId < m_layers.size() ); wxASSERT( (unsigned) aRequiredId < m_layers.size() ); if( aRequired ) m_layers[aLayerId].requiredLayers.insert( aRequiredId ); else m_layers[aLayerId].requiredLayers.erase( aRequired ); } // stupid C++... python lambda would do this in one line template struct queryVisitor { typedef typename Container::value_type item_type; queryVisitor( Container& aCont, int aLayer ) : m_cont( aCont ), m_layer( aLayer ) { } bool operator()( VIEW_ITEM* aItem ) { if( aItem->viewPrivData()->getFlags() & VISIBLE ) m_cont.push_back( VIEW::LAYER_ITEM_PAIR( aItem, m_layer ) ); return true; } Container& m_cont; int m_layer; }; int VIEW::Query( const BOX2I& aRect, std::vector& aResult ) const { if( m_orderedLayers.empty() ) return 0; std::vector::const_reverse_iterator i; // execute queries in reverse direction, so that items that are on the top of // the rendering stack are returned first. for( i = m_orderedLayers.rbegin(); i != m_orderedLayers.rend(); ++i ) { // ignore layers that do not contain actual items (i.e. the selection box, menus, floats) if( ( *i )->displayOnly ) continue; queryVisitor > visitor( aResult, ( *i )->id ); ( *i )->items->Query( aRect, visitor ); } return aResult.size(); } VECTOR2D VIEW::ToWorld( const VECTOR2D& aCoord, bool aAbsolute ) const { const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix(); if( aAbsolute ) return VECTOR2D( matrix * aCoord ); else return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y ); } double VIEW::ToWorld( double aSize ) const { const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix(); return fabs( matrix.GetScale().x * aSize ); } VECTOR2D VIEW::ToScreen( const VECTOR2D& aCoord, bool aAbsolute ) const { const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix(); if( aAbsolute ) return VECTOR2D( matrix * aCoord ); else return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y ); } double VIEW::ToScreen( double aSize ) const { const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix(); return matrix.GetScale().x * aSize; } void VIEW::CopySettings( const VIEW* aOtherView ) { wxASSERT_MSG( false, wxT( "This is not implemented" ) ); } void VIEW::SetGAL( GAL* aGal ) { m_gal = aGal; // clear group numbers, so everything is going to be recached clearGroupCache(); // every target has to be refreshed MarkDirty(); // force the new GAL to display the current viewport. SetCenter( m_center ); SetScale( m_scale ); SetMirror( m_mirrorX, m_mirrorY ); } BOX2D VIEW::GetViewport() const { BOX2D rect; VECTOR2D screenSize = m_gal->GetScreenPixelSize(); rect.SetOrigin( ToWorld( VECTOR2D( 0, 0 ) ) ); rect.SetEnd( ToWorld( screenSize ) ); return rect.Normalize(); } void VIEW::SetViewport( const BOX2D& aViewport ) { VECTOR2D ssize = ToWorld( m_gal->GetScreenPixelSize(), false ); wxASSERT( ssize.x > 0 && ssize.y > 0 ); VECTOR2D centre = aViewport.Centre(); VECTOR2D vsize = aViewport.GetSize(); double zoom = 1.0 / std::max( fabs( vsize.x / ssize.x ), fabs( vsize.y / ssize.y ) ); SetCenter( centre ); SetScale( GetScale() * zoom ); } void VIEW::SetMirror( bool aMirrorX, bool aMirrorY ) { wxASSERT_MSG( !aMirrorY, _( "Mirroring for Y axis is not supported yet" ) ); m_mirrorX = aMirrorX; m_mirrorY = aMirrorY; m_gal->SetFlip( aMirrorX, aMirrorY ); // Redraw everything MarkDirty(); } void VIEW::SetScale( double aScale, const VECTOR2D& aAnchor ) { VECTOR2D a = ToScreen( aAnchor ); if( aScale < m_minScale ) m_scale = m_minScale; else if( aScale > m_maxScale ) m_scale = m_maxScale; else m_scale = aScale; m_gal->SetZoomFactor( m_scale ); m_gal->ComputeWorldScreenMatrix(); VECTOR2D delta = ToWorld( a ) - aAnchor; SetCenter( m_center - delta ); // Redraw everything after the viewport has changed MarkDirty(); } void VIEW::SetCenter( const VECTOR2D& aCenter ) { m_center = aCenter; if( !m_boundary.Contains( aCenter ) ) { if( m_center.x < m_boundary.GetLeft() ) m_center.x = m_boundary.GetLeft(); else if( aCenter.x > m_boundary.GetRight() ) m_center.x = m_boundary.GetRight(); if( m_center.y < m_boundary.GetTop() ) m_center.y = m_boundary.GetTop(); else if( m_center.y > m_boundary.GetBottom() ) m_center.y = m_boundary.GetBottom(); } m_gal->SetLookAtPoint( m_center ); m_gal->ComputeWorldScreenMatrix(); // Redraw everything after the viewport has changed MarkDirty(); } void VIEW::SetCenter( VECTOR2D aCenter, const BOX2D& occultingScreenRect ) { BOX2D screenRect( VECTOR2D( 0, 0 ), m_gal->GetScreenPixelSize() ); if( !screenRect.Intersects( occultingScreenRect ) ) { SetCenter( aCenter ); return; } BOX2D occultedRect = screenRect.Intersect( occultingScreenRect ); VECTOR2D offset( occultedRect.GetWidth() / 2, occultedRect.GetHeight() / 2 ); double topExposed = occultedRect.GetTop() - screenRect.GetTop(); double bottomExposed = screenRect.GetBottom() - occultedRect.GetBottom(); double leftExposed = occultedRect.GetLeft() - screenRect.GetLeft(); double rightExposed = screenRect.GetRight() - occultedRect.GetRight(); if( std::max( topExposed, bottomExposed ) > std::max( leftExposed, rightExposed ) ) { if( topExposed > bottomExposed ) aCenter.y += ToWorld( occultedRect.GetHeight() / 2 ); else aCenter.y -= ToWorld( occultedRect.GetHeight() / 2 ); } else { if( leftExposed > rightExposed ) aCenter.x += ToWorld( occultedRect.GetWidth() / 2 ); else aCenter.x -= ToWorld( occultedRect.GetWidth() / 2 ); } SetCenter( aCenter ); } void VIEW::SetLayerOrder( int aLayer, int aRenderingOrder ) { m_layers[aLayer].renderingOrder = aRenderingOrder; sortLayers(); } int VIEW::GetLayerOrder( int aLayer ) const { return m_layers.at( aLayer ).renderingOrder; } void VIEW::SortLayers( int aLayers[], int& aCount ) const { int maxLay, maxOrd, maxIdx; for( int i = 0; i < aCount; ++i ) { maxLay = aLayers[i]; maxOrd = GetLayerOrder( maxLay ); maxIdx = i; // Look for the max element in the range (j..aCount) for( int j = i; j < aCount; ++j ) { if( maxOrd < GetLayerOrder( aLayers[j] ) ) { maxLay = aLayers[j]; maxOrd = GetLayerOrder( maxLay ); maxIdx = j; } } // Swap elements aLayers[maxIdx] = aLayers[i]; aLayers[i] = maxLay; } } void VIEW::ReorderLayerData( std::unordered_map aReorderMap ) { LAYER_MAP new_map; for( auto it : m_layers ) { int orig_idx = it.first; VIEW_LAYER layer = it.second; int new_idx; try { new_idx = aReorderMap.at( orig_idx ); } catch( const std::out_of_range& ) { new_idx = orig_idx; } layer.id = new_idx; new_map[new_idx] = layer; } m_layers = new_map; for( VIEW_ITEM* item : m_allItems ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; int layers[VIEW::VIEW_MAX_LAYERS], layers_count; item->ViewGetLayers( layers, layers_count ); viewData->saveLayers( layers, layers_count ); viewData->reorderGroups( aReorderMap ); viewData->m_requiredUpdate |= COLOR; } UpdateItems(); } struct VIEW::updateItemsColor { updateItemsColor( int aLayer, PAINTER* aPainter, GAL* aGal ) : layer( aLayer ), painter( aPainter ), gal( aGal ) { } bool operator()( VIEW_ITEM* aItem ) { // Obtain the color that should be used for coloring the item const COLOR4D color = painter->GetSettings()->GetColor( aItem, layer ); int group = aItem->viewPrivData()->getGroup( layer ); if( group >= 0 ) gal->ChangeGroupColor( group, color ); return true; } int layer; PAINTER* painter; GAL* gal; }; void VIEW::UpdateLayerColor( int aLayer ) { // There is no point in updating non-cached layers if( !IsCached( aLayer ) ) return; BOX2I r; r.SetMaximum(); m_gal->BeginUpdate(); updateItemsColor visitor( aLayer, m_painter, m_gal ); m_layers[aLayer].items->Query( r, visitor ); MarkTargetDirty( m_layers[aLayer].target ); m_gal->EndUpdate(); } void VIEW::UpdateAllLayersColor() { m_gal->BeginUpdate(); for( VIEW_ITEM* item : m_allItems ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; int layers[VIEW::VIEW_MAX_LAYERS], layers_count; viewData->getLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { const COLOR4D color = m_painter->GetSettings()->GetColor( item, layers[i] ); int group = viewData->getGroup( layers[i] ); if( group >= 0 ) m_gal->ChangeGroupColor( group, color ); } } m_gal->EndUpdate(); MarkDirty(); } struct VIEW::changeItemsDepth { changeItemsDepth( int aLayer, int aDepth, GAL* aGal ) : layer( aLayer ), depth( aDepth ), gal( aGal ) { } bool operator()( VIEW_ITEM* aItem ) { int group = aItem->viewPrivData()->getGroup( layer ); if( group >= 0 ) gal->ChangeGroupDepth( group, depth ); return true; } int layer, depth; GAL* gal; }; int VIEW::GetTopLayer() const { if( m_topLayers.size() == 0 ) return 0; return *m_topLayers.begin(); } void VIEW::SetTopLayer( int aLayer, bool aEnabled ) { if( aEnabled ) { if( m_topLayers.count( aLayer ) == 1 ) return; m_topLayers.insert( aLayer ); // Move the layer closer to front if( m_enableOrderModifier ) m_layers[aLayer].renderingOrder += TOP_LAYER_MODIFIER; } else { if( m_topLayers.count( aLayer ) == 0 ) return; m_topLayers.erase( aLayer ); // Restore the previous rendering order if( m_enableOrderModifier ) m_layers[aLayer].renderingOrder -= TOP_LAYER_MODIFIER; } } void VIEW::EnableTopLayer( bool aEnable ) { if( aEnable == m_enableOrderModifier ) return; m_enableOrderModifier = aEnable; std::set::iterator it; if( aEnable ) { for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) m_layers[*it].renderingOrder += TOP_LAYER_MODIFIER; } else { for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER; } UpdateAllLayersOrder(); UpdateAllLayersColor(); } void VIEW::ClearTopLayers() { std::set::iterator it; if( m_enableOrderModifier ) { // Restore the previous rendering order for layers that were marked as top for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER; } m_topLayers.clear(); } void VIEW::UpdateAllLayersOrder() { sortLayers(); m_gal->BeginUpdate(); for( VIEW_ITEM* item : m_allItems ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; int layers[VIEW::VIEW_MAX_LAYERS], layers_count; viewData->getLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { int group = viewData->getGroup( layers[i] ); if( group >= 0 ) m_gal->ChangeGroupDepth( group, m_layers[layers[i]].renderingOrder ); } } m_gal->EndUpdate(); MarkDirty(); } struct VIEW::drawItem { drawItem( VIEW* aView, int aLayer, bool aUseDrawPriority, bool aReverseDrawOrder ) : view( aView ), layer( aLayer ), useDrawPriority( aUseDrawPriority ), reverseDrawOrder( aReverseDrawOrder ) { } bool operator()( VIEW_ITEM* aItem ) { wxASSERT( aItem->viewPrivData() ); // Conditions that have to be fulfilled for an item to be drawn bool drawCondition = aItem->viewPrivData()->isRenderable() && aItem->ViewGetLOD( layer, view ) < view->m_scale; if( !drawCondition ) return true; if( useDrawPriority ) drawItems.push_back( aItem ); else view->draw( aItem, layer ); return true; } void deferredDraw() { if( reverseDrawOrder ) std::sort( drawItems.begin(), drawItems.end(), []( VIEW_ITEM* a, VIEW_ITEM* b ) -> bool { return b->viewPrivData()->m_drawPriority < a->viewPrivData()->m_drawPriority; }); else std::sort( drawItems.begin(), drawItems.end(), []( VIEW_ITEM* a, VIEW_ITEM* b ) -> bool { return a->viewPrivData()->m_drawPriority < b->viewPrivData()->m_drawPriority; }); for( auto item : drawItems ) view->draw( item, layer ); } VIEW* view; int layer, layers[VIEW_MAX_LAYERS]; bool useDrawPriority, reverseDrawOrder; std::vector drawItems; }; void VIEW::redrawRect( const BOX2I& aRect ) { for( VIEW_LAYER* l : m_orderedLayers ) { if( l->visible && IsTargetDirty( l->target ) && areRequiredLayersEnabled( l->id ) ) { drawItem drawFunc( this, l->id, m_useDrawPriority, m_reverseDrawOrder ); m_gal->SetTarget( l->target ); m_gal->SetLayerDepth( l->renderingOrder ); l->items->Query( aRect, drawFunc ); if( m_useDrawPriority ) drawFunc.deferredDraw(); } } } void VIEW::draw( VIEW_ITEM* aItem, int aLayer, bool aImmediate ) { auto viewData = aItem->viewPrivData(); if( !viewData ) return; if( IsCached( aLayer ) && !aImmediate ) { // Draw using cached information or create one int group = viewData->getGroup( aLayer ); if( group >= 0 ) m_gal->DrawGroup( group ); else Update( aItem ); } else { // Immediate mode if( !m_painter->Draw( aItem, aLayer ) ) aItem->ViewDraw( aLayer, this ); // Alternative drawing method } } void VIEW::draw( VIEW_ITEM* aItem, bool aImmediate ) { int layers[VIEW_MAX_LAYERS], layers_count; aItem->ViewGetLayers( layers, layers_count ); // Sorting is needed for drawing order dependent GALs (like Cairo) SortLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { m_gal->SetLayerDepth( m_layers.at( layers[i] ).renderingOrder ); draw( aItem, layers[i], aImmediate ); } } void VIEW::draw( VIEW_GROUP* aGroup, bool aImmediate ) { for( unsigned int i = 0; i < aGroup->GetSize(); i++) draw( aGroup->GetItem(i), aImmediate ); } struct VIEW::recacheItem { recacheItem( VIEW* aView, GAL* aGal, int aLayer ) : view( aView ), gal( aGal ), layer( aLayer ) { } bool operator()( VIEW_ITEM* aItem ) { auto viewData = aItem->viewPrivData(); if( !viewData ) return false; // Remove previously cached group int group = viewData->getGroup( layer ); if( group >= 0 ) gal->DeleteGroup( group ); viewData->setGroup( layer, -1 ); view->Update( aItem ); return true; } VIEW* view; GAL* gal; int layer; }; void VIEW::Clear() { BOX2I r; r.SetMaximum(); m_allItems.clear(); for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) i->second.items->RemoveAll(); m_nextDrawPriority = 0; m_gal->ClearCache(); } void VIEW::ClearTargets() { if( IsTargetDirty( TARGET_CACHED ) || IsTargetDirty( TARGET_NONCACHED ) ) { // TARGET_CACHED and TARGET_NONCACHED have to be redrawn together, as they contain // layers that rely on each other (eg. netnames are noncached, but tracks - are cached) m_gal->ClearTarget( TARGET_NONCACHED ); m_gal->ClearTarget( TARGET_CACHED ); MarkDirty(); } if( IsTargetDirty( TARGET_OVERLAY ) ) { m_gal->ClearTarget( TARGET_OVERLAY ); } } void VIEW::Redraw() { #ifdef __WXDEBUG__ PROF_COUNTER totalRealTime; #endif /* __WXDEBUG__ */ VECTOR2D screenSize = m_gal->GetScreenPixelSize(); BOX2I rect( ToWorld( VECTOR2D( 0, 0 ) ), ToWorld( screenSize ) - ToWorld( VECTOR2D( 0, 0 ) ) ); rect.Normalize(); redrawRect( rect ); // All targets were redrawn, so nothing is dirty markTargetClean( TARGET_CACHED ); markTargetClean( TARGET_NONCACHED ); markTargetClean( TARGET_OVERLAY ); #ifdef __WXDEBUG__ totalRealTime.Stop(); wxLogTrace( "GAL_PROFILE", "VIEW::Redraw(): %.1f ms", totalRealTime.msecs() ); #endif /* __WXDEBUG__ */ } const VECTOR2I& VIEW::GetScreenPixelSize() const { return m_gal->GetScreenPixelSize(); } struct VIEW::clearLayerCache { clearLayerCache( VIEW* aView ) : view( aView ) { } bool operator()( VIEW_ITEM* aItem ) { aItem->viewPrivData()->deleteGroups(); return true; } VIEW* view; }; void VIEW::clearGroupCache() { BOX2I r; r.SetMaximum(); clearLayerCache visitor( this ); for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) { VIEW_LAYER* l = &( ( *i ).second ); l->items->Query( r, visitor ); } } void VIEW::invalidateItem( VIEW_ITEM* aItem, int aUpdateFlags ) { if( aUpdateFlags & INITIAL_ADD ) { // Don't update layers or bbox, since it was done in VIEW::Add() // Now that we have initialized, set flags to ALL for the code below aUpdateFlags = ALL; } else { // updateLayers updates geometry too, so we do not have to update both of them at the same time if( aUpdateFlags & LAYERS ) { updateLayers( aItem ); } else if( aUpdateFlags & GEOMETRY ) { updateBbox( aItem ); } } int layers[VIEW_MAX_LAYERS], layers_count; aItem->ViewGetLayers( layers, layers_count ); // Iterate through layers used by the item and recache it immediately for( int i = 0; i < layers_count; ++i ) { int layerId = layers[i]; if( IsCached( layerId ) ) { if( aUpdateFlags & ( GEOMETRY | LAYERS | REPAINT ) ) updateItemGeometry( aItem, layerId ); else if( aUpdateFlags & COLOR ) updateItemColor( aItem, layerId ); } // Mark those layers as dirty, so the VIEW will be refreshed MarkTargetDirty( m_layers[layerId].target ); } aItem->viewPrivData()->clearUpdateFlags(); } void VIEW::sortLayers() { int n = 0; m_orderedLayers.resize( m_layers.size() ); for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) m_orderedLayers[n++] = &i->second; sort( m_orderedLayers.begin(), m_orderedLayers.end(), compareRenderingOrder ); MarkDirty(); } void VIEW::updateItemColor( VIEW_ITEM* aItem, int aLayer ) { auto viewData = aItem->viewPrivData(); wxASSERT( (unsigned) aLayer < m_layers.size() ); wxASSERT( IsCached( aLayer ) ); if( !viewData ) return; // Obtain the color that should be used for coloring the item on the specific layerId const COLOR4D color = m_painter->GetSettings()->GetColor( aItem, aLayer ); int group = viewData->getGroup( aLayer ); // Change the color, only if it has group assigned if( group >= 0 ) m_gal->ChangeGroupColor( group, color ); } void VIEW::updateItemGeometry( VIEW_ITEM* aItem, int aLayer ) { auto viewData = aItem->viewPrivData(); wxASSERT( (unsigned) aLayer < m_layers.size() ); wxASSERT( IsCached( aLayer ) ); if( !viewData ) return; VIEW_LAYER& l = m_layers.at( aLayer ); m_gal->SetTarget( l.target ); m_gal->SetLayerDepth( l.renderingOrder ); // Redraw the item from scratch int group = viewData->getGroup( aLayer ); if( group >= 0 ) m_gal->DeleteGroup( group ); group = m_gal->BeginGroup(); viewData->setGroup( aLayer, group ); if( !m_painter->Draw( static_cast( aItem ), aLayer ) ) aItem->ViewDraw( aLayer, this ); // Alternative drawing method m_gal->EndGroup(); } void VIEW::updateBbox( VIEW_ITEM* aItem ) { int layers[VIEW_MAX_LAYERS], layers_count; aItem->ViewGetLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { VIEW_LAYER& l = m_layers[layers[i]]; l.items->Remove( aItem ); l.items->Insert( aItem ); MarkTargetDirty( l.target ); } } void VIEW::updateLayers( VIEW_ITEM* aItem ) { auto viewData = aItem->viewPrivData(); int layers[VIEW_MAX_LAYERS], layers_count; if( !viewData ) return; // Remove the item from previous layer set viewData->getLayers( layers, layers_count ); for( int i = 0; i < layers_count; ++i ) { VIEW_LAYER& l = m_layers[layers[i]]; l.items->Remove( aItem ); MarkTargetDirty( l.target ); if( IsCached( l.id ) ) { // Redraw the item from scratch int prevGroup = viewData->getGroup( layers[i] ); if( prevGroup >= 0 ) { m_gal->DeleteGroup( prevGroup ); viewData->setGroup( l.id, -1 ); } } } // Add the item to new layer set aItem->ViewGetLayers( layers, layers_count ); viewData->saveLayers( layers, layers_count ); for( int i = 0; i < layers_count; i++ ) { VIEW_LAYER& l = m_layers[layers[i]]; l.items->Insert( aItem ); MarkTargetDirty( l.target ); } } bool VIEW::areRequiredLayersEnabled( int aLayerId ) const { wxASSERT( (unsigned) aLayerId < m_layers.size() ); std::set::const_iterator it, it_end; for( it = m_layers.at( aLayerId ).requiredLayers.begin(), it_end = m_layers.at( aLayerId ).requiredLayers.end(); it != it_end; ++it ) { // That is enough if just one layer is not enabled if( !m_layers.at( *it ).visible || !areRequiredLayersEnabled( *it ) ) return false; } return true; } void VIEW::RecacheAllItems() { BOX2I r; r.SetMaximum(); for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) { VIEW_LAYER* l = &( ( *i ).second ); if( IsCached( l->id ) ) { recacheItem visitor( this, m_gal, l->id ); l->items->Query( r, visitor ); } } } void VIEW::UpdateItems() { m_gal->BeginUpdate(); for( VIEW_ITEM* item : m_allItems ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; if( viewData->m_requiredUpdate != NONE ) { invalidateItem( item, viewData->m_requiredUpdate ); viewData->m_requiredUpdate = NONE; } } m_gal->EndUpdate(); } void VIEW::UpdateAllItems( int aUpdateFlags ) { for( VIEW_ITEM* item : m_allItems ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; viewData->m_requiredUpdate |= aUpdateFlags; } } void VIEW::UpdateAllItemsConditionally( int aUpdateFlags, std::function aCondition ) { for( VIEW_ITEM* item : m_allItems ) { if( aCondition( item ) ) { auto viewData = item->viewPrivData(); if( !viewData ) continue; viewData->m_requiredUpdate |= aUpdateFlags; } } } struct VIEW::extentsVisitor { BOX2I extents; bool first; extentsVisitor() { first = true; } bool operator()( VIEW_ITEM* aItem ) { if( first ) extents = aItem->ViewBBox(); else extents.Merge( aItem->ViewBBox() ); return false; } }; const BOX2I VIEW::CalculateExtents() { extentsVisitor v; BOX2I fullScene; fullScene.SetMaximum(); for( VIEW_LAYER* l : m_orderedLayers ) { l->items->Query( fullScene, v ); } return v.extents; } void VIEW::SetVisible( VIEW_ITEM* aItem, bool aIsVisible ) { auto viewData = aItem->viewPrivData(); if( !viewData ) return; bool cur_visible = viewData->m_flags & VISIBLE; if( cur_visible != aIsVisible ) { if( aIsVisible ) viewData->m_flags |= VISIBLE; else viewData->m_flags &= ~VISIBLE; Update( aItem, APPEARANCE | COLOR ); } } void VIEW::Hide( VIEW_ITEM* aItem, bool aHide ) { auto viewData = aItem->viewPrivData(); if( !viewData ) return; if( !( viewData->m_flags & VISIBLE ) ) return; if( aHide ) viewData->m_flags |= HIDDEN; else viewData->m_flags &= ~HIDDEN; Update( aItem, APPEARANCE ); } bool VIEW::IsVisible( const VIEW_ITEM* aItem ) const { const auto viewData = aItem->viewPrivData(); return viewData->m_flags & VISIBLE; } void VIEW::Update( VIEW_ITEM* aItem ) { Update( aItem, ALL ); } void VIEW::Update( VIEW_ITEM* aItem, int aUpdateFlags ) { auto viewData = aItem->viewPrivData(); if( !viewData ) return; assert( aUpdateFlags != NONE ); viewData->m_requiredUpdate |= aUpdateFlags; } std::shared_ptr VIEW::MakeOverlay() { std::shared_ptr overlay( new VIEW_OVERLAY ); Add( overlay.get() ); return overlay; } const int VIEW::TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS; }