kicad/common/preview_items/ruler_item.cpp

377 lines
12 KiB
C++
Raw Normal View History

/*
* 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>
2020-10-14 03:37:48 +00:00
#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,
2022-09-16 04:38:10 +00:00
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;
2022-09-16 04:38:10 +00:00
cursorStrings.push_back( DimensionLabel( "x", temp.x, aIuScale, aUnits ) );
cursorStrings.push_back( DimensionLabel( "y", temp.y, aIuScale, aUnits ) );
2022-09-16 04:38:10 +00:00
cursorStrings.push_back( DimensionLabel( "r", aRulerVec.EuclideanNorm(), aIuScale, aUnits ) );
2022-01-20 18:10:14 +00:00
EDA_ANGLE angle = -EDA_ANGLE( aRulerVec );
2022-09-16 04:38:10 +00:00
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)
};
2019-12-20 14:11:39 +00:00
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
2021-04-05 13:24:57 +00:00
* 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,
2022-09-16 04:38:10 +00:00
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 );
2022-01-20 18:10:14 +00:00
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 );
2022-01-20 18:10:14 +00:00
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
2022-01-20 18:10:14 +00:00
if( EDA_ANGLE( aLine ) > ANGLE_0 )
{
labelAttrs.m_Halign = GR_TEXT_H_ALIGN_LEFT;
2022-01-20 18:10:14 +00:00
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;
2022-01-20 18:10:14 +00:00
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 )
{
2020-11-07 21:21:56 +00:00
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 )
{
2022-09-16 04:38:10 +00:00
wxString label = DimensionLabel( "", tickSpace * i, aIuScale, aUnits, false );
font->Draw( gal, label, tickPos + labelOffset, labelAttrs );
}
}
}
/**
* 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 )
{
2022-01-20 18:10:14 +00:00
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;
2022-01-20 18:10:14 +00:00
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 );
}
}
2022-09-16 04:38:10 +00:00
RULER_ITEM::RULER_ITEM( const TWO_POINT_GEOMETRY_MANAGER& aGeomMgr, const EDA_IU_SCALE& aIuScale, EDA_UNITS userUnits,
bool aFlipX, bool aFlipY )
2019-12-20 14:11:39 +00:00
: EDA_ITEM( NOT_USED ), // Never added to anything - just a preview
m_geomMgr( aGeomMgr ),
m_userUnits( userUnits ),
2022-09-16 04:38:10 +00:00
m_iuScale( aIuScale ),
m_flipX( aFlipX ),
m_flipY( aFlipY )
2019-12-20 14:11:39 +00:00
{
}
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() );
2019-02-15 14:35:31 +00:00
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 );
2022-09-16 04:38:10 +00:00
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;
2022-09-16 04:38:10 +00:00
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();
}