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