1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017-2021 Kicad Developers, see change_log.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24 #include <preview_items/ruler_item.h>
25 #include <preview_items/preview_utils.h>
26 #include <gal/graphics_abstraction_layer.h>
27 #include <layer_ids.h>
28 #include <painter.h>
29 #include <view/view.h>
30 #include <trigo.h>
31
32 using namespace KIGFX::PREVIEW;
33
34 static const double maxTickDensity = 10.0; // min pixels between tick marks
35 static const double midTickLengthFactor = 1.5;
36 static const double majorTickLengthFactor = 2.5;
37
38
39 /*
40 * It would be nice to know why Cairo seems to have an opposite layer order from GAL, but
41 * only when drawing RULER_ITEMs (the TWO_POINT_ASSISTANT and ARC_ASSISTANT are immune from
42 * this issue).
43 *
44 * Until then, this egregious hack...
45 */
getShadowLayer(KIGFX::GAL * aGal)46 static int getShadowLayer( KIGFX::GAL* aGal )
47 {
48 if( aGal->IsCairoEngine() )
49 return LAYER_SELECT_OVERLAY;
50 else
51 return LAYER_GP_OVERLAY;
52 }
53
54
drawCursorStrings(KIGFX::VIEW * aView,const VECTOR2D & aCursor,const VECTOR2D & aRulerVec,EDA_UNITS aUnits,bool aDrawingDropShadows,bool aFlipX,bool aFlipY)55 static void drawCursorStrings( KIGFX::VIEW* aView, const VECTOR2D& aCursor,
56 const VECTOR2D& aRulerVec, EDA_UNITS aUnits,
57 bool aDrawingDropShadows, bool aFlipX, bool aFlipY )
58 {
59 // draw the cursor labels
60 std::vector<wxString> cursorStrings;
61
62 VECTOR2D temp = aRulerVec;
63
64 if( aFlipX )
65 temp.x = -temp.x;
66
67 if( aFlipY )
68 temp.y = -temp.y;
69
70 cursorStrings.push_back( DimensionLabel( "x", temp.x, aUnits ) );
71 cursorStrings.push_back( DimensionLabel( "y", temp.y, aUnits ) );
72
73 cursorStrings.push_back( DimensionLabel( "r", aRulerVec.EuclideanNorm(), aUnits ) );
74
75 double degs = RAD2DECIDEG( -aRulerVec.Angle() );
76 cursorStrings.push_back( DimensionLabel( wxString::FromUTF8( "θ" ), degs,
77 EDA_UNITS::DEGREES ) );
78
79 temp = aRulerVec;
80 DrawTextNextToCursor( aView, aCursor, -temp, cursorStrings, aDrawingDropShadows );
81 }
82
83
getTickLineWidth(const TEXT_DIMS & textDims,bool aDrawingDropShadows)84 static double getTickLineWidth( const TEXT_DIMS& textDims, bool aDrawingDropShadows )
85 {
86 double width = textDims.StrokeWidth * 0.8;
87
88 if( aDrawingDropShadows )
89 width += textDims.ShadowWidth;
90
91 return width;
92 }
93
94
95 /**
96 * Description of a "tick format" for a scale factor - how many ticks there are
97 * between medium/major ticks and how each scale relates to the last one
98 */
99 struct TICK_FORMAT
100 {
101 double divisionBase; ///< multiple from the last scale
102 int majorStep; ///< ticks between major ticks
103 int midStep; ///< ticks between medium ticks (0 if no medium ticks)
104 };
105
106
getTickFormatForScale(double aScale,double & aTickSpace,EDA_UNITS aUnits)107 static TICK_FORMAT getTickFormatForScale( double aScale, double& aTickSpace, EDA_UNITS aUnits )
108 {
109 // simple 1/2/5 scales per decade
110 static std::vector<TICK_FORMAT> tickFormats =
111 {
112 { 2, 10, 5 }, // |....:....|
113 { 2, 5, 0 }, // |....|
114 { 2.5, 2, 0 }, // |.|.|
115 };
116
117 // could start at a set number of MM, but that's not available in common
118 aTickSpace = 1;
119
120 // Convert to a round (mod-10) number of mils for imperial units
121 if( EDA_UNIT_UTILS::IsImperialUnit( aUnits ) )
122 aTickSpace *= 2.54;
123
124 int tickFormat = 0;
125
126 while( true )
127 {
128 const auto pixelSpace = aTickSpace * aScale;
129
130 if( pixelSpace >= maxTickDensity )
131 break;
132
133 tickFormat = ( tickFormat + 1 ) % tickFormats.size();
134 aTickSpace *= tickFormats[tickFormat].divisionBase;
135 }
136
137 return tickFormats[tickFormat];
138 }
139
140
141 /**
142 * Draw labelled ticks on a line. Ticks are spaced according to a
143 * maximum density. Minor ticks are not labelled.
144 *
145 * @param aGal the GAL to draw on
146 * @param aOrigin start of line to draw ticks on
147 * @param aLine line vector
148 * @param aMinorTickLen length of minor ticks in IU
149 */
drawTicksAlongLine(KIGFX::VIEW * aView,const VECTOR2D & aOrigin,const VECTOR2D & aLine,double aMinorTickLen,EDA_UNITS aUnits,bool aDrawingDropShadows)150 void drawTicksAlongLine( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
151 double aMinorTickLen, EDA_UNITS aUnits, bool aDrawingDropShadows )
152 {
153 KIGFX::GAL* gal = aView->GetGAL();
154 VECTOR2D tickLine = aLine.Rotate( -M_PI_2 );
155 double tickSpace;
156 TICK_FORMAT tickF = getTickFormatForScale( gal->GetWorldScale(), tickSpace, aUnits );
157
158 // number of ticks in whole ruler
159 int numTicks = (int) std::ceil( aLine.EuclideanNorm() / tickSpace );
160
161 // work out which way up the tick labels go
162 TEXT_DIMS textDims = SetConstantGlyphHeight( gal, -1 );
163 double textThickness = textDims.StrokeWidth;
164 double labelAngle = -tickLine.Angle();
165 double textOffset = 0;
166
167 if( aDrawingDropShadows )
168 {
169 textOffset = textDims.ShadowWidth;
170 textThickness += 2 * textDims.ShadowWidth;
171 }
172
173 double majorTickLen = aMinorTickLen * ( majorTickLengthFactor + 1 );
174 VECTOR2D labelOffset = tickLine.Resize( majorTickLen - textOffset );
175
176 if( aView->IsMirroredX() )
177 {
178 textOffset = -textOffset;
179 labelOffset = -labelOffset;
180 }
181
182 if( aLine.Angle() > 0 )
183 {
184 gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_LEFT );
185 }
186 else
187 {
188 gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_RIGHT );
189 labelAngle += M_PI;
190 }
191
192 BOX2D viewportD = aView->GetViewport();
193 BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
194
195 viewport.Inflate( majorTickLen * 2 ); // Doesn't have to be accurate, just big enough not
196 // to exclude anything that should be partially drawn
197
198 for( int i = 0; i < numTicks; ++i )
199 {
200 const VECTOR2D tickPos = aOrigin + aLine.Resize( tickSpace * i );
201
202 if( !viewport.Contains( tickPos ) )
203 continue;
204
205 double length = aMinorTickLen;
206 bool drawLabel = false;
207
208 if( i % tickF.majorStep == 0 )
209 {
210 drawLabel = true;
211 length *= majorTickLengthFactor;
212 }
213 else if( tickF.midStep && i % tickF.midStep == 0 )
214 {
215 drawLabel = true;
216 length *= midTickLengthFactor;
217 }
218
219 gal->SetLineWidth( textThickness / 2 );
220 gal->DrawLine( tickPos, tickPos + tickLine.Resize( length ) );
221
222 if( drawLabel )
223 {
224 wxString label = DimensionLabel( "", tickSpace * i, aUnits, false );
225 gal->SetLineWidth( textThickness );
226 gal->StrokeText( label, tickPos + labelOffset, labelAngle );
227 }
228 }
229 }
230
231
232 /**
233 * Draw simple ticks on the back of a line such that the line is
234 * divided into n parts.
235 *
236 * @param aGal the GAL to draw on
237 * @param aOrigin start of line to draw ticks on
238 * @param aLine line vector
239 * @param aTickLen length of ticks in IU
240 * @param aNumDivisions number of parts to divide the line into
241 */
drawBacksideTicks(KIGFX::VIEW * aView,const VECTOR2D & aOrigin,const VECTOR2D & aLine,double aTickLen,int aNumDivisions,bool aDrawingDropShadows)242 void drawBacksideTicks( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
243 double aTickLen, int aNumDivisions, bool aDrawingDropShadows )
244 {
245 KIGFX::GAL* gal = aView->GetGAL();
246 const double backTickSpace = aLine.EuclideanNorm() / aNumDivisions;
247 const VECTOR2D backTickVec = aLine.Rotate( M_PI_2 ).Resize( aTickLen );
248 TEXT_DIMS textDims = SetConstantGlyphHeight( gal, -1 );
249
250 BOX2D viewportD = aView->GetViewport();
251 BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
252
253 viewport.Inflate( aTickLen * 4 ); // Doesn't have to be accurate, just big enough not to
254 // exclude anything that should be partially drawn
255
256 for( int i = 0; i < aNumDivisions + 1; ++i )
257 {
258 const VECTOR2D backTickPos = aOrigin + aLine.Resize( backTickSpace * i );
259
260 if( !viewport.Contains( backTickPos ) )
261 continue;
262
263 gal->SetLineWidth( getTickLineWidth( textDims, aDrawingDropShadows ) );
264 gal->DrawLine( backTickPos, backTickPos + backTickVec );
265 }
266 }
267
268
RULER_ITEM(const TWO_POINT_GEOMETRY_MANAGER & aGeomMgr,EDA_UNITS userUnits,bool aFlipX,bool aFlipY)269 RULER_ITEM::RULER_ITEM( const TWO_POINT_GEOMETRY_MANAGER& aGeomMgr, EDA_UNITS userUnits,
270 bool aFlipX, bool aFlipY )
271 : EDA_ITEM( NOT_USED ), // Never added to anything - just a preview
272 m_geomMgr( aGeomMgr ),
273 m_userUnits( userUnits ),
274 m_flipX( aFlipX ),
275 m_flipY( aFlipY )
276 {
277 }
278
279
ViewBBox() const280 const BOX2I RULER_ITEM::ViewBBox() const
281 {
282 BOX2I tmp;
283
284 if( m_geomMgr.GetOrigin() == m_geomMgr.GetEnd() )
285 return tmp;
286
287 // this is an edit-time artefact; no reason to try and be smart with the bounding box
288 // (besides, we can't tell the text extents without a view to know what the scale is)
289 tmp.SetMaximum();
290 return tmp;
291 }
292
293
ViewGetLayers(int aLayers[],int & aCount) const294 void RULER_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
295 {
296 aLayers[0] = LAYER_SELECT_OVERLAY;
297 aLayers[1] = LAYER_GP_OVERLAY;
298 aCount = 2;
299 }
300
301
ViewDraw(int aLayer,KIGFX::VIEW * aView) const302 void RULER_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
303 {
304 KIGFX::GAL* gal = aView->GetGAL();
305 RENDER_SETTINGS* rs = aView->GetPainter()->GetSettings();
306 bool drawingDropShadows = ( aLayer == getShadowLayer( gal ) );
307
308 gal->PushDepth();
309 gal->SetLayerDepth( gal->GetMinDepth() );
310
311 VECTOR2D origin = m_geomMgr.GetOrigin();
312 VECTOR2D end = m_geomMgr.GetEnd();
313
314 gal->SetIsStroke( true );
315 gal->SetIsFill( false );
316
317 gal->SetTextMirrored( false );
318 gal->SetStrokeColor( rs->GetLayerColor( LAYER_AUX_ITEMS ) );
319
320 if( drawingDropShadows )
321 gal->SetStrokeColor( GetShadowColor( gal->GetStrokeColor() ) );
322
323 gal->ResetTextAttributes();
324 TEXT_DIMS textDims = SetConstantGlyphHeight( gal );
325
326 // draw the main line from the origin to cursor
327 gal->SetLineWidth( getTickLineWidth( textDims, drawingDropShadows ) );
328 gal->DrawLine( origin, end );
329
330 VECTOR2D rulerVec( end - origin );
331
332 drawCursorStrings( aView, end, rulerVec, m_userUnits, drawingDropShadows, m_flipX, m_flipY );
333
334 // basic tick size
335 const double minorTickLen = 5.0 / gal->GetWorldScale();
336 const double majorTickLen = minorTickLen * majorTickLengthFactor;
337
338 drawTicksAlongLine( aView, origin, rulerVec, minorTickLen, m_userUnits, drawingDropShadows );
339
340 drawBacksideTicks( aView, origin, rulerVec, majorTickLen, 2, drawingDropShadows );
341
342 // draw the back of the origin "crosshair"
343 gal->DrawLine( origin, origin + rulerVec.Resize( -minorTickLen * midTickLengthFactor ) );
344 gal->PopDepth();
345 }
346