377 lines
12 KiB
C++
377 lines
12 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2017-2023 Kicad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* 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 <preview_items/ruler_item.h>
|
|
#include <preview_items/preview_utils.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <layer_ids.h>
|
|
#include <painter.h>
|
|
#include <view/view.h>
|
|
#include <trigo.h>
|
|
|
|
using namespace KIGFX::PREVIEW;
|
|
|
|
static const double maxTickDensity = 10.0; // min pixels between tick marks
|
|
static const double midTickLengthFactor = 1.5;
|
|
static const double majorTickLengthFactor = 2.5;
|
|
|
|
|
|
/*
|
|
* It would be nice to know why Cairo seems to have an opposite layer order from GAL, but
|
|
* only when drawing RULER_ITEMs (the TWO_POINT_ASSISTANT and ARC_ASSISTANT are immune from
|
|
* this issue).
|
|
*
|
|
* Until then, this egregious hack...
|
|
*/
|
|
static int getShadowLayer( KIGFX::GAL* aGal )
|
|
{
|
|
if( aGal->IsCairoEngine() )
|
|
return LAYER_SELECT_OVERLAY;
|
|
else
|
|
return LAYER_GP_OVERLAY;
|
|
}
|
|
|
|
|
|
static void drawCursorStrings( KIGFX::VIEW* aView, const VECTOR2D& aCursor,
|
|
const VECTOR2D& aRulerVec, const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits,
|
|
bool aDrawingDropShadows, bool aFlipX, bool aFlipY )
|
|
{
|
|
// draw the cursor labels
|
|
std::vector<wxString> cursorStrings;
|
|
|
|
VECTOR2D temp = aRulerVec;
|
|
|
|
if( aFlipX )
|
|
temp.x = -temp.x;
|
|
|
|
if( aFlipY )
|
|
temp.y = -temp.y;
|
|
|
|
cursorStrings.push_back( DimensionLabel( "x", temp.x, aIuScale, aUnits ) );
|
|
cursorStrings.push_back( DimensionLabel( "y", temp.y, aIuScale, aUnits ) );
|
|
|
|
cursorStrings.push_back( DimensionLabel( "r", aRulerVec.EuclideanNorm(), aIuScale, aUnits ) );
|
|
|
|
EDA_ANGLE angle = -EDA_ANGLE( aRulerVec );
|
|
cursorStrings.push_back( DimensionLabel( wxString::FromUTF8( "θ" ), angle.AsDegrees(), aIuScale,
|
|
EDA_UNITS::DEGREES ) );
|
|
|
|
temp = aRulerVec;
|
|
DrawTextNextToCursor( aView, aCursor, -temp, cursorStrings, aDrawingDropShadows );
|
|
}
|
|
|
|
|
|
static double getTickLineWidth( const TEXT_DIMS& textDims, bool aDrawingDropShadows )
|
|
{
|
|
double width = textDims.StrokeWidth * 0.8;
|
|
|
|
if( aDrawingDropShadows )
|
|
width += textDims.ShadowWidth;
|
|
|
|
return width;
|
|
}
|
|
|
|
|
|
/**
|
|
* Description of a "tick format" for a scale factor - how many ticks there are
|
|
* between medium/major ticks and how each scale relates to the last one
|
|
*/
|
|
struct TICK_FORMAT
|
|
{
|
|
double divisionBase; ///< multiple from the last scale
|
|
int majorStep; ///< ticks between major ticks
|
|
int midStep; ///< ticks between medium ticks (0 if no medium ticks)
|
|
};
|
|
|
|
|
|
static TICK_FORMAT getTickFormatForScale( double aScale, double& aTickSpace, EDA_UNITS aUnits )
|
|
{
|
|
// simple 1/2/5 scales per decade
|
|
static std::vector<TICK_FORMAT> tickFormats =
|
|
{
|
|
{ 2, 10, 5 }, // |....:....|
|
|
{ 2, 5, 0 }, // |....|
|
|
{ 2.5, 2, 0 }, // |.|.|
|
|
};
|
|
|
|
// could start at a set number of MM, but that's not available in common
|
|
aTickSpace = 1;
|
|
|
|
// Convert to a round (mod-10) number of mils for imperial units
|
|
if( EDA_UNIT_UTILS::IsImperialUnit( aUnits ) )
|
|
aTickSpace *= 2.54;
|
|
|
|
int tickFormat = 0;
|
|
|
|
while( true )
|
|
{
|
|
const auto pixelSpace = aTickSpace * aScale;
|
|
|
|
if( pixelSpace >= maxTickDensity )
|
|
break;
|
|
|
|
tickFormat = ( tickFormat + 1 ) % tickFormats.size();
|
|
aTickSpace *= tickFormats[tickFormat].divisionBase;
|
|
}
|
|
|
|
return tickFormats[tickFormat];
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw labelled ticks on a line. Ticks are spaced according to a
|
|
* maximum density. Minor ticks are not labelled.
|
|
*
|
|
* @param aGal the GAL to draw on
|
|
* @param aOrigin start of line to draw ticks on
|
|
* @param aLine line vector
|
|
* @param aMinorTickLen length of minor ticks in IU
|
|
*/
|
|
void drawTicksAlongLine( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
|
|
double aMinorTickLen, const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits, bool aDrawingDropShadows )
|
|
{
|
|
KIGFX::GAL* gal = aView->GetGAL();
|
|
KIFONT::FONT* font = KIFONT::FONT::GetFont();
|
|
double tickSpace;
|
|
TICK_FORMAT tickFormat = getTickFormatForScale( gal->GetWorldScale(), tickSpace, aUnits );
|
|
double majorTickLen = aMinorTickLen * ( majorTickLengthFactor + 1 );
|
|
VECTOR2D tickLine = aLine;
|
|
|
|
RotatePoint( tickLine, ANGLE_90 );
|
|
|
|
// number of ticks in whole ruler
|
|
int numTicks = (int) std::ceil( aLine.EuclideanNorm() / tickSpace );
|
|
|
|
// work out which way up the tick labels go
|
|
TEXT_DIMS labelDims = GetConstantGlyphHeight( gal, -1 );
|
|
EDA_ANGLE labelAngle = - EDA_ANGLE( tickLine );
|
|
VECTOR2I labelOffset = tickLine.Resize( majorTickLen );
|
|
|
|
// text is left (or right) aligned, so shadow text need a small offset to be draw
|
|
// around the basic text
|
|
int shadowXoffset = 0;
|
|
|
|
if( aDrawingDropShadows )
|
|
{
|
|
labelDims.StrokeWidth += 2 * labelDims.ShadowWidth;
|
|
shadowXoffset = labelDims.ShadowWidth;
|
|
// Due to the fact a shadow text is drawn left or right aligned,
|
|
// it needs an offset = shadowXoffset to be drawn at the same place as normal text
|
|
// But for some reason we need to slightly modify this offset
|
|
// for a better look for KiCad font (better alignment of shadow shape)
|
|
const float adjust = 1.2f; // Value chosen after tests
|
|
shadowXoffset *= adjust;
|
|
}
|
|
|
|
if( aView->IsMirroredX() )
|
|
{
|
|
labelOffset = -labelOffset;
|
|
shadowXoffset = -shadowXoffset;
|
|
}
|
|
|
|
TEXT_ATTRIBUTES labelAttrs;
|
|
labelAttrs.m_Size = labelDims.GlyphSize;
|
|
labelAttrs.m_StrokeWidth = labelDims.StrokeWidth;
|
|
labelAttrs.m_Mirrored = aView->IsMirroredX(); // Prevent text mirrored when view is mirrored
|
|
|
|
if( EDA_ANGLE( aLine ) > ANGLE_0 )
|
|
{
|
|
labelAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
|
|
labelAttrs.m_Angle = labelAngle;
|
|
|
|
// Adjust the text position of the shadow shape:
|
|
labelOffset.x -= shadowXoffset * labelAttrs.m_Angle.Cos();;
|
|
labelOffset.y += shadowXoffset * labelAttrs.m_Angle.Sin();;
|
|
}
|
|
else
|
|
{
|
|
labelAttrs.m_Halign = GR_TEXT_H_ALIGN_RIGHT;
|
|
labelAttrs.m_Angle = labelAngle + ANGLE_180;
|
|
|
|
// Adjust the text position of the shadow shape:
|
|
labelOffset.x += shadowXoffset * labelAttrs.m_Angle.Cos();;
|
|
labelOffset.y -= shadowXoffset * labelAttrs.m_Angle.Sin();;
|
|
}
|
|
|
|
BOX2D viewportD = aView->GetViewport();
|
|
BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
|
|
|
|
viewport.Inflate( majorTickLen * 2 ); // Doesn't have to be accurate, just big enough not
|
|
// to exclude anything that should be partially drawn
|
|
|
|
int isign = aView->IsMirroredX() ? -1 : 1;
|
|
|
|
for( int i = 0; i < numTicks; ++i )
|
|
{
|
|
const VECTOR2D tickPos = aOrigin + aLine.Resize( tickSpace * i );
|
|
|
|
if( !viewport.Contains( tickPos ) )
|
|
continue;
|
|
|
|
double length = aMinorTickLen;
|
|
bool drawLabel = false;
|
|
|
|
if( i % tickFormat.majorStep == 0 )
|
|
{
|
|
drawLabel = true;
|
|
length *= majorTickLengthFactor;
|
|
}
|
|
else if( tickFormat.midStep && i % tickFormat.midStep == 0 )
|
|
{
|
|
drawLabel = true;
|
|
length *= midTickLengthFactor;
|
|
}
|
|
|
|
gal->SetLineWidth( labelAttrs.m_StrokeWidth / 2 );
|
|
gal->DrawLine( tickPos, tickPos + tickLine.Resize( length*isign ) );
|
|
|
|
if( drawLabel )
|
|
{
|
|
wxString label = DimensionLabel( "", tickSpace * i, aIuScale, aUnits, false );
|
|
font->Draw( gal, label, tickPos + labelOffset, labelAttrs, KIFONT::METRICS::Default() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw simple ticks on the back of a line such that the line is
|
|
* divided into n parts.
|
|
*
|
|
* @param aGal the GAL to draw on
|
|
* @param aOrigin start of line to draw ticks on
|
|
* @param aLine line vector
|
|
* @param aTickLen length of ticks in IU
|
|
* @param aNumDivisions number of parts to divide the line into
|
|
*/
|
|
void drawBacksideTicks( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
|
|
double aTickLen, int aNumDivisions, bool aDrawingDropShadows )
|
|
{
|
|
KIGFX::GAL* gal = aView->GetGAL();
|
|
TEXT_DIMS textDims = GetConstantGlyphHeight( gal, -1 );
|
|
const double backTickSpace = aLine.EuclideanNorm() / aNumDivisions;
|
|
VECTOR2D backTickVec = aLine;
|
|
int isign = aView->IsMirroredX() ? -1 : 1;
|
|
|
|
RotatePoint( backTickVec, -ANGLE_90 );
|
|
backTickVec = backTickVec.Resize( aTickLen * isign );
|
|
|
|
BOX2D viewportD = aView->GetViewport();
|
|
BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
|
|
|
|
viewport.Inflate( aTickLen * 4 ); // Doesn't have to be accurate, just big enough not to
|
|
// exclude anything that should be partially drawn
|
|
|
|
for( int i = 0; i < aNumDivisions + 1; ++i )
|
|
{
|
|
const VECTOR2D backTickPos = aOrigin + aLine.Resize( backTickSpace * i );
|
|
|
|
if( !viewport.Contains( backTickPos ) )
|
|
continue;
|
|
|
|
gal->SetLineWidth( getTickLineWidth( textDims, aDrawingDropShadows ) );
|
|
gal->DrawLine( backTickPos, backTickPos + backTickVec );
|
|
}
|
|
}
|
|
|
|
|
|
RULER_ITEM::RULER_ITEM( const TWO_POINT_GEOMETRY_MANAGER& aGeomMgr, const EDA_IU_SCALE& aIuScale, EDA_UNITS userUnits,
|
|
bool aFlipX, bool aFlipY )
|
|
: EDA_ITEM( NOT_USED ), // Never added to anything - just a preview
|
|
m_geomMgr( aGeomMgr ),
|
|
m_userUnits( userUnits ),
|
|
m_iuScale( aIuScale ),
|
|
m_flipX( aFlipX ),
|
|
m_flipY( aFlipY )
|
|
{
|
|
}
|
|
|
|
|
|
const BOX2I RULER_ITEM::ViewBBox() const
|
|
{
|
|
BOX2I tmp;
|
|
|
|
if( m_geomMgr.GetOrigin() == m_geomMgr.GetEnd() )
|
|
return tmp;
|
|
|
|
// this is an edit-time artefact; no reason to try and be smart with the bounding box
|
|
// (besides, we can't tell the text extents without a view to know what the scale is)
|
|
tmp.SetMaximum();
|
|
return tmp;
|
|
}
|
|
|
|
|
|
void RULER_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
|
|
{
|
|
aLayers[0] = LAYER_SELECT_OVERLAY;
|
|
aLayers[1] = LAYER_GP_OVERLAY;
|
|
aCount = 2;
|
|
}
|
|
|
|
|
|
void RULER_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
|
|
{
|
|
KIGFX::GAL* gal = aView->GetGAL();
|
|
RENDER_SETTINGS* rs = aView->GetPainter()->GetSettings();
|
|
bool drawingDropShadows = ( aLayer == getShadowLayer( gal ) );
|
|
|
|
gal->PushDepth();
|
|
gal->SetLayerDepth( gal->GetMinDepth() );
|
|
|
|
VECTOR2D origin = m_geomMgr.GetOrigin();
|
|
VECTOR2D end = m_geomMgr.GetEnd();
|
|
|
|
gal->SetIsStroke( true );
|
|
gal->SetIsFill( false );
|
|
|
|
gal->SetTextMirrored( false );
|
|
gal->SetStrokeColor( rs->GetLayerColor( LAYER_AUX_ITEMS ) );
|
|
|
|
if( drawingDropShadows )
|
|
gal->SetStrokeColor( GetShadowColor( gal->GetStrokeColor() ) );
|
|
|
|
gal->ResetTextAttributes();
|
|
TEXT_DIMS textDims = GetConstantGlyphHeight( gal );
|
|
|
|
// draw the main line from the origin to cursor
|
|
gal->SetLineWidth( getTickLineWidth( textDims, drawingDropShadows ) );
|
|
gal->DrawLine( origin, end );
|
|
|
|
VECTOR2D rulerVec( end - origin );
|
|
|
|
drawCursorStrings( aView, end, rulerVec, m_iuScale, m_userUnits, drawingDropShadows, m_flipX,
|
|
m_flipY );
|
|
|
|
// basic tick size
|
|
const double minorTickLen = 5.0 / gal->GetWorldScale();
|
|
const double majorTickLen = minorTickLen * majorTickLengthFactor;
|
|
|
|
drawTicksAlongLine( aView, origin, rulerVec, minorTickLen, m_iuScale, m_userUnits, drawingDropShadows );
|
|
|
|
drawBacksideTicks( aView, origin, rulerVec, majorTickLen, 2, drawingDropShadows );
|
|
|
|
// draw the back of the origin "crosshair"
|
|
gal->DrawLine( origin, origin + rulerVec.Resize( -minorTickLen * midTickLengthFactor ) );
|
|
gal->PopDepth();
|
|
}
|