/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2012 Torsten Hueter, torstenhtr gmx.de * Copyright (C) 2012-2017 Kicad Developers, see change_log.txt for contributors. * * Graphics Abstraction Layer (GAL) - base class * * 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 using namespace KIGFX; const double GAL::METRIC_UNIT_LENGTH = 1e9; GAL::GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions ) : options( aDisplayOptions ), strokeFont( this ) { // Set the default values for the internal variables SetIsFill( false ); SetIsStroke( true ); SetFillColor( COLOR4D( 0.0, 0.0, 0.0, 0.0 ) ); SetStrokeColor( COLOR4D( 1.0, 1.0, 1.0, 1.0 ) ); SetLookAtPoint( VECTOR2D( 0, 0 ) ); SetZoomFactor( 1.0 ); SetRotation( 0.0 ); SetWorldUnitLength( 1.0 / METRIC_UNIT_LENGTH * 2.54 ); // 1 inch in nanometers SetScreenDPI( 106 ); // Display resolution setting SetDepthRange( VECTOR2D( GAL::MIN_DEPTH, GAL::MAX_DEPTH ) ); SetLayerDepth( 0.0 ); SetFlip( false, false ); SetLineWidth( 1.0 ); computeWorldScale(); SetAxesEnabled( false ); // Set grid defaults SetGridVisibility( true ); SetCoarseGrid( 10 ); gridLineWidth = 0.5; gridStyle = GRID_STYLE::LINES; gridMinSpacing = 10; // Initialize the cursor shape SetCursorColor( COLOR4D( 1.0, 1.0, 1.0, 1.0 ) ); fullscreenCursor = false; forceDisplayCursor = false; SetCursorEnabled( false ); // Initialize text properties ResetTextAttributes(); strokeFont.LoadNewStrokeFont( newstroke_font, newstroke_font_bufsize ); // subscribe for settings updates observerLink = options.Subscribe( this ); } GAL::~GAL() { } void GAL::OnGalDisplayOptionsChanged( const GAL_DISPLAY_OPTIONS& aOptions ) { // defer to the child class first updatedGalDisplayOptions( aOptions ); // there is no refresh to do at this level } bool GAL::updatedGalDisplayOptions( const GAL_DISPLAY_OPTIONS& aOptions ) { bool refresh = false; if( options.m_gridStyle != gridStyle ) { gridStyle = options.m_gridStyle ; refresh = true; } if( options.m_gridLineWidth != gridLineWidth ) { gridLineWidth = options.m_gridLineWidth ; refresh = true; } if( options.m_gridMinSpacing != gridMinSpacing ) { gridMinSpacing = options.m_gridMinSpacing; refresh = true; } if( options.m_axesEnabled != axesEnabled ) { axesEnabled = options.m_axesEnabled; refresh = true; } if( options.m_forceDisplayCursor != forceDisplayCursor ) { forceDisplayCursor = options.m_forceDisplayCursor; refresh = true; } if( options.m_fullscreenCursor != fullscreenCursor ) { fullscreenCursor = options.m_fullscreenCursor; refresh = true; } // tell the derived class if the base class needs an update or not return refresh; } void GAL::SetTextAttributes( const EDA_TEXT* aText ) { SetGlyphSize( VECTOR2D( aText->GetTextSize() ) ); SetHorizontalJustify( aText->GetHorizJustify() ); SetVerticalJustify( aText->GetVertJustify() ); SetFontBold( aText->IsBold() ); SetFontItalic( aText->IsItalic() ); SetTextMirrored( aText->IsMirrored() ); } void GAL::ResetTextAttributes() { // Tiny but non-zero - this will always need setting // there is no built-in default SetGlyphSize( { 1.0, 1.0 } ); SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER ); SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER ); SetFontBold( false ); SetFontItalic( false ); SetTextMirrored( false ); } VECTOR2D GAL::GetTextLineSize( const UTF8& aText ) const { // Compute the X and Y size of a given text. // Because computeTextLineSize expects a one line text, // aText is expected to be only one line text. return strokeFont.computeTextLineSize( aText ); } void GAL::ComputeWorldScreenMatrix() { computeWorldScale(); MATRIX3x3D translation; translation.SetIdentity(); translation.SetTranslation( 0.5 * VECTOR2D( screenSize ) ); MATRIX3x3D rotate; rotate.SetIdentity(); rotate.SetRotation( rotation ); MATRIX3x3D scale; scale.SetIdentity(); scale.SetScale( VECTOR2D( worldScale, worldScale ) ); MATRIX3x3D flip; flip.SetIdentity(); flip.SetScale( VECTOR2D( globalFlipX ? -1.0 : 1.0, globalFlipY ? -1.0 : 1.0 ) ); MATRIX3x3D lookat; lookat.SetIdentity(); lookat.SetTranslation( -lookAtPoint ); worldScreenMatrix = translation * rotate * flip * scale * lookat; screenWorldMatrix = worldScreenMatrix.Inverse(); } double GAL::computeMinGridSpacing() const { // just return the current value. This could be cleverer and take // into account other settings in future return gridMinSpacing; } void GAL::DrawGrid() { SetTarget( TARGET_NONCACHED ); // Draw the grid // For the drawing the start points, end points and increments have // to be calculated in world coordinates VECTOR2D worldStartPoint = screenWorldMatrix * VECTOR2D( 0.0, 0.0 ); VECTOR2D worldEndPoint = screenWorldMatrix * VECTOR2D( screenSize ); const double gridThreshold = computeMinGridSpacing(); int gridScreenSizeDense = KiROUND( gridSize.x * worldScale ); int gridScreenSizeCoarse = KiROUND( gridSize.x * static_cast( gridTick ) * worldScale ); // Compute the line marker or point radius of the grid // Note: generic grids can't handle sub-pixel lines without // either losing fine/course distinction or having some dots // fail to render double marker = std::max( 1.0, gridLineWidth ) / worldScale; double doubleMarker = 2.0 * marker; // Draw axes if desired if( axesEnabled ) { SetIsFill( false ); SetIsStroke( true ); SetStrokeColor( axesColor ); SetLineWidth( marker ); drawGridLine( VECTOR2D( worldStartPoint.x, 0 ), VECTOR2D( worldEndPoint.x, 0 ) ); drawGridLine( VECTOR2D( 0, worldStartPoint.y ), VECTOR2D( 0, worldEndPoint.y ) ); } if( !gridVisibility ) return; // Check if the grid would not be too dense if( std::max( gridScreenSizeDense, gridScreenSizeCoarse ) <= gridThreshold ) return; // Compute grid staring and ending indexes to draw grid points on the // visible screen area // Note: later any point coordinate will be offsetted by gridOrigin int gridStartX = KiROUND( ( worldStartPoint.x - gridOrigin.x ) / gridSize.x ); int gridEndX = KiROUND( ( worldEndPoint.x - gridOrigin.x ) / gridSize.x ); int gridStartY = KiROUND( ( worldStartPoint.y - gridOrigin.y ) / gridSize.y ); int gridEndY = KiROUND( ( worldEndPoint.y - gridOrigin.y ) / gridSize.y ); // Ensure start coordinate > end coordinate if( gridStartX > gridEndX ) std::swap( gridStartX, gridEndX ); if( gridStartY > gridEndY ) std::swap( gridStartY, gridEndY ); // Ensure the grid fills the screen --gridStartX; ++gridEndX; --gridStartY; ++gridEndY; // Draw the grid behind all other layers SetLayerDepth( depthRange.y * 0.75 ); if( gridStyle == GRID_STYLE::LINES ) { SetIsFill( false ); SetIsStroke( true ); SetStrokeColor( gridColor ); // Now draw the grid, every coarse grid line gets the double width // Vertical lines for( int j = gridStartY; j <= gridEndY; j++ ) { const double y = j * gridSize.y + gridOrigin.y; if( axesEnabled && y == 0 ) continue; if( j % gridTick == 0 && gridScreenSizeDense > gridThreshold ) SetLineWidth( doubleMarker ); else SetLineWidth( marker ); if( ( j % gridTick == 0 && gridScreenSizeCoarse > gridThreshold ) || gridScreenSizeDense > gridThreshold ) { drawGridLine( VECTOR2D( gridStartX * gridSize.x + gridOrigin.x, y ), VECTOR2D( gridEndX * gridSize.x + gridOrigin.x, y ) ); } } // Horizontal lines for( int i = gridStartX; i <= gridEndX; i++ ) { const double x = i * gridSize.x + gridOrigin.x; if( axesEnabled && x == 0 ) continue; if( i % gridTick == 0 && gridScreenSizeDense > gridThreshold ) SetLineWidth( doubleMarker ); else SetLineWidth( marker ); if( ( i % gridTick == 0 && gridScreenSizeCoarse > gridThreshold ) || gridScreenSizeDense > gridThreshold ) { drawGridLine( VECTOR2D( x, gridStartY * gridSize.y + gridOrigin.y ), VECTOR2D( x, gridEndY * gridSize.y + gridOrigin.y ) ); } } } else if( gridStyle == GRID_STYLE::SMALL_CROSS ) { SetIsFill( false ); SetIsStroke( true ); SetStrokeColor( gridColor ); SetLineWidth( marker ); double lineLen = GetLineWidth() * 2; // Vertical positions: for( int j = gridStartY; j <= gridEndY; j++ ) { if( ( j % gridTick == 0 && gridScreenSizeCoarse > gridThreshold ) || gridScreenSizeDense > gridThreshold ) { int posY = j * gridSize.y + gridOrigin.y; // Horizontal positions: for( int i = gridStartX; i <= gridEndX; i++ ) { if( ( i % gridTick == 0 && gridScreenSizeCoarse > gridThreshold ) || gridScreenSizeDense > gridThreshold ) { int posX = i * gridSize.x + gridOrigin.x; drawGridLine( VECTOR2D( posX - lineLen, posY ), VECTOR2D( posX + lineLen, posY ) ); drawGridLine( VECTOR2D( posX, posY - lineLen ), VECTOR2D( posX, posY + lineLen ) ); } } } } } else // Dotted grid { bool tickX, tickY; SetIsFill( true ); SetIsStroke( false ); SetFillColor( gridColor ); for( int j = gridStartY; j <= gridEndY; j++ ) { if( j % gridTick == 0 && gridScreenSizeDense > gridThreshold ) tickY = true; else tickY = false; for( int i = gridStartX; i <= gridEndX; i++ ) { if( i % gridTick == 0 && gridScreenSizeDense > gridThreshold ) tickX = true; else tickX = false; if( tickX || tickY || gridScreenSizeDense > gridThreshold ) { double radius = ( ( tickX && tickY ) ? doubleMarker : marker ) / 2.0; DrawRectangle( VECTOR2D( i * gridSize.x - radius + gridOrigin.x, j * gridSize.y - radius + gridOrigin.y ), VECTOR2D( i * gridSize.x + radius + gridOrigin.x, j * gridSize.y + radius + gridOrigin.y ) ); } } } } } VECTOR2D GAL::GetGridPoint( const VECTOR2D& aPoint ) const { #if 0 // This old code expects a non zero grid size, which can be wrong here. return VECTOR2D( KiROUND( ( aPoint.x - gridOffset.x ) / gridSize.x ) * gridSize.x + gridOffset.x, KiROUND( ( aPoint.y - gridOffset.y ) / gridSize.y ) * gridSize.y + gridOffset.y ); #else // if grid size == 0.0 there is no grid, so use aPoint as grid reference position double cx = gridSize.x > 0.0 ? KiROUND( ( aPoint.x - gridOffset.x ) / gridSize.x ) * gridSize.x + gridOffset.x : aPoint.x; double cy = gridSize.y > 0.0 ? KiROUND( ( aPoint.y - gridOffset.y ) / gridSize.y ) * gridSize.y + gridOffset.y : aPoint.y; return VECTOR2D( cx, cy ); #endif } const int GAL::MIN_DEPTH = -1024; const int GAL::MAX_DEPTH = 1023; const int GAL::GRID_DEPTH = MAX_DEPTH - 1; COLOR4D GAL::getCursorColor() const { auto color = cursorColor; // dim the cursor if it's only on because it was forced // (this helps to provide a hint for active tools) if( !isCursorEnabled ) { color.a = color.a * 0.5; } return color; }