1 /*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2014-2019 CERN
5 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <maciej.suminski@cern.ch>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26 #include <gal/graphics_abstraction_layer.h>
27 #include <gal/color4d.h>
28 #include <painter.h>
29 #include <math/util.h> // for KiROUND
30 #include "tool/edit_points.h"
31
32
WithinPoint(const VECTOR2I & aPoint,unsigned int aSize) const33 bool EDIT_POINT::WithinPoint( const VECTOR2I& aPoint, unsigned int aSize ) const
34 {
35 // Corners of the EDIT_POINT square
36 VECTOR2I topLeft = GetPosition() - aSize;
37 VECTOR2I bottomRight = GetPosition() + aSize;
38
39 return ( aPoint.x > topLeft.x && aPoint.y > topLeft.y &&
40 aPoint.x < bottomRight.x && aPoint.y < bottomRight.y );
41 }
42
43
EDIT_POINTS(EDA_ITEM * aParent)44 EDIT_POINTS::EDIT_POINTS( EDA_ITEM* aParent )
45 : EDA_ITEM( NOT_USED ), m_parent( aParent ), m_allowPoints( true )
46 {
47 }
48
49
FindPoint(const VECTOR2I & aLocation,KIGFX::VIEW * aView)50 EDIT_POINT* EDIT_POINTS::FindPoint( const VECTOR2I& aLocation, KIGFX::VIEW *aView ) // fixme: ugly
51 {
52 unsigned size = std::abs( KiROUND( aView->ToWorld( EDIT_POINT::POINT_SIZE ) ) );
53
54 if( m_allowPoints )
55 {
56 for( EDIT_POINT& point : m_points )
57 {
58 if( point.WithinPoint( aLocation, size ) )
59 return &point;
60 }
61 }
62
63 for( EDIT_LINE& line : m_lines )
64 {
65 if( line.WithinPoint( aLocation, size ) )
66 return &line;
67 }
68
69 return nullptr;
70 }
71
72
GetContourStartIdx(int aPointIdx) const73 int EDIT_POINTS::GetContourStartIdx( int aPointIdx ) const
74 {
75 int lastIdx = 0;
76
77 for( int idx : m_contours )
78 {
79 if( idx >= aPointIdx )
80 return lastIdx;
81
82 lastIdx = idx + 1;
83 }
84
85 return lastIdx;
86 }
87
88
GetContourEndIdx(int aPointIdx) const89 int EDIT_POINTS::GetContourEndIdx( int aPointIdx ) const
90 {
91 for( int idx : m_contours )
92 {
93 if( idx >= aPointIdx )
94 return idx;
95 }
96
97 return m_points.size() - 1;
98 }
99
100
IsContourStart(int aPointIdx) const101 bool EDIT_POINTS::IsContourStart( int aPointIdx ) const
102 {
103 for( int idx : m_contours )
104 {
105 if( idx + 1 == aPointIdx )
106 return true;
107
108 // the list is sorted, so we cannot expect it any further
109 if( idx > aPointIdx )
110 break;
111 }
112
113 return ( aPointIdx == 0 );
114 }
115
116
IsContourEnd(int aPointIdx) const117 bool EDIT_POINTS::IsContourEnd( int aPointIdx ) const
118 {
119 for( int idx : m_contours )
120 {
121 if( idx == aPointIdx )
122 return true;
123
124 // the list is sorted, so we cannot expect it any further
125 if( idx > aPointIdx )
126 break;
127 }
128
129 // the end of the list surely is the end of a contour
130 return ( aPointIdx == (int) m_points.size() - 1 );
131 }
132
133
Previous(const EDIT_POINT & aPoint,bool aTraverseContours)134 EDIT_POINT* EDIT_POINTS::Previous( const EDIT_POINT& aPoint, bool aTraverseContours )
135 {
136 for( unsigned int i = 0; i < m_points.size(); ++i )
137 {
138 if( m_points[i] == aPoint )
139 {
140 if( !aTraverseContours && IsContourStart( i ) )
141 return &m_points[GetContourEndIdx( i )];
142
143 if( i == 0 )
144 return &m_points[m_points.size() - 1];
145 else
146 return &m_points[i - 1];
147 }
148 }
149
150 return nullptr;
151 }
152
153
Previous(const EDIT_LINE & aLine)154 EDIT_LINE* EDIT_POINTS::Previous( const EDIT_LINE& aLine )
155 {
156 for( unsigned int i = 0; i < m_lines.size(); ++i )
157 {
158 if( m_lines[i] == aLine )
159 {
160 if( i == 0 )
161 return &m_lines[m_lines.size() - 1];
162 else
163 return &m_lines[i - 1];
164 }
165 }
166
167 return nullptr;
168 }
169
170
Next(const EDIT_POINT & aPoint,bool aTraverseContours)171 EDIT_POINT* EDIT_POINTS::Next( const EDIT_POINT& aPoint, bool aTraverseContours )
172 {
173 for( unsigned int i = 0; i < m_points.size(); ++i )
174 {
175 if( m_points[i] == aPoint )
176 {
177 if( !aTraverseContours && IsContourEnd( i ) )
178 return &m_points[GetContourStartIdx( i )];
179
180 if( i == m_points.size() - 1 )
181 return &m_points[0];
182 else
183 return &m_points[i + 1];
184 }
185 }
186
187 return nullptr;
188 }
189
190
Next(const EDIT_LINE & aLine)191 EDIT_LINE* EDIT_POINTS::Next( const EDIT_LINE& aLine )
192 {
193 for( unsigned int i = 0; i < m_lines.size(); ++i )
194 {
195 if( m_lines[i] == aLine )
196 {
197 if( i == m_lines.size() - 1 )
198 return &m_lines[0];
199 else
200 return &m_lines[i + 1];
201 }
202 }
203
204 return nullptr;
205 }
206
207
ViewBBox() const208 const BOX2I EDIT_POINTS::ViewBBox() const
209 {
210 BOX2I box;
211 bool empty = true;
212
213 for( const auto& point : m_points )
214 {
215 if( empty )
216 {
217 box.SetOrigin( point.GetPosition() );
218 empty = false;
219 }
220 else
221 {
222 box.Merge( point.GetPosition() );
223 }
224 }
225
226 for( const auto& line : m_lines )
227 {
228 if( empty )
229 {
230 box.SetOrigin( line.GetOrigin().GetPosition() );
231 box.SetEnd( line.GetEnd().GetPosition() );
232 empty = false;
233 }
234 else
235 {
236 box.Merge( line.GetOrigin().GetPosition() );
237 box.Merge( line.GetEnd().GetPosition() );
238 }
239 }
240
241 return box;
242 }
243
244
ViewDraw(int aLayer,KIGFX::VIEW * aView) const245 void EDIT_POINTS::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
246 {
247 KIGFX::GAL* gal = aView->GetGAL();
248 KIGFX::RENDER_SETTINGS* settings = aView->GetPainter()->GetSettings();
249 KIGFX::COLOR4D drawColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
250
251 // Don't assume LAYER_AUX_ITEMS is always a good choice. Compare with background.
252 if( aView->GetGAL()->GetClearColor().Distance( drawColor ) < 0.5 )
253 drawColor.Invert();
254
255 // Linear darkening doesn't fit well with human color perception, and there's no guarantee
256 // that there's enough room for contrast either.
257 KIGFX::COLOR4D borderColor;
258 KIGFX::COLOR4D highlightColor;
259 double brightness = drawColor.GetBrightness();
260
261 if( brightness > 0.5 )
262 {
263 borderColor = drawColor.Darkened( 0.3 ).WithAlpha( 0.8 );
264 highlightColor = drawColor.Brightened( 0.6 ).WithAlpha( 0.8 );
265 }
266 else if( brightness > 0.2 )
267 {
268 borderColor = drawColor.Darkened( 0.6 ).WithAlpha( 0.8 );
269 highlightColor = drawColor.Brightened( 0.3 ).WithAlpha( 0.8 );
270 }
271 else
272 {
273 borderColor = drawColor.Brightened( 0.3 ).WithAlpha( 0.8 );
274 highlightColor = drawColor.Brightened( 0.6 ).WithAlpha( 0.8 );
275 }
276
277 gal->SetFillColor( drawColor );
278 gal->SetStrokeColor( borderColor );
279 gal->SetIsFill( true );
280 gal->SetIsStroke( true );
281 gal->PushDepth();
282 gal->SetLayerDepth( gal->GetMinDepth() );
283
284 double size = aView->ToWorld( EDIT_POINT::POINT_SIZE ) / 2.0;
285 double borderSize = aView->ToWorld( EDIT_POINT::BORDER_SIZE );
286 double hoverSize = aView->ToWorld( EDIT_POINT::HOVER_SIZE );
287
288 auto drawPoint =
289 [&]( const EDIT_POINT& aPoint, bool aDrawCircle = false )
290 {
291 if( aPoint.IsHover() || aPoint.IsActive() )
292 {
293 gal->SetStrokeColor( highlightColor );
294 gal->SetLineWidth( hoverSize );
295 }
296 else
297 {
298 gal->SetStrokeColor( borderColor );
299 gal->SetLineWidth( borderSize );
300 }
301
302 gal->SetFillColor( drawColor );
303
304 if( aDrawCircle )
305 gal->DrawCircle( aPoint.GetPosition(), size );
306 else
307 gal->DrawRectangle( aPoint.GetPosition() - size, aPoint.GetPosition() + size );
308 };
309
310 for( const EDIT_POINT& point : m_points )
311 drawPoint( point );
312
313 for( const EDIT_LINE& line : m_lines )
314 drawPoint( line, true );
315
316 gal->PopDepth();
317 }
318