KiCad PCB EDA Suite
Loading...
Searching...
No Matches
color_swatch.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <kiplatform/ui.h>
22#include <wx/dcmemory.h>
23#include <wx/weakref.h>
24
25#include <dpi_scaling_common.h>
27#include <memory>
28
29wxDEFINE_EVENT( COLOR_SWATCH_CHANGED, wxCommandEvent );
30
31using KIGFX::COLOR4D;
32
33
34wxBitmap COLOR_SWATCH::MakeBitmap( const COLOR4D& aColor, const COLOR4D& aBackground, const wxSize& aSize,
35 const wxSize& aCheckerboardSize, const COLOR4D& aCheckerboardBackground,
36 const std::vector<int>& aMargins )
37{
38 wxBitmap bitmap( aSize );
39 wxMemoryDC iconDC;
40
41 iconDC.SelectObject( bitmap );
42
43 RenderToDC( &iconDC, aColor, aBackground, aSize, aCheckerboardSize, aCheckerboardBackground, aMargins );
44
45 return bitmap;
46}
47
48
50{
51 wxBitmap bitmap = COLOR_SWATCH::MakeBitmap( m_color, m_background, ToPhys( m_size ),
53
54 bitmap.SetScaleFactor( GetDPIScaleFactor() );
55 return bitmap;
56}
57
58
59void COLOR_SWATCH::RenderToDC( wxDC* aDC, const KIGFX::COLOR4D& aColor, const KIGFX::COLOR4D& aBackground,
60 const wxRect& aRect, const wxSize& aCheckerboardSize,
61 const KIGFX::COLOR4D& aCheckerboardBackground, const std::vector<int>& aMargins )
62{
63 wxColor fg = aColor.m_text ? COLOR4D::UNSPECIFIED.ToColour() : aColor.ToColour();
64
65 wxBrush brush;
66 brush.SetStyle( wxBRUSHSTYLE_SOLID );
67
68 aDC->SetPen( *wxTRANSPARENT_PEN );
69
70 // Draw a checkerboard
71 COLOR4D white;
72 COLOR4D black;
73 bool rowCycle;
74
75 if( aColor.m_text || aColor == COLOR4D::UNSPECIFIED )
76 {
77 if( aCheckerboardBackground.GetBrightness() > 0.4 )
78 {
79 white = COLOR4D::WHITE;
80 black = white.Darkened( 0.15 );
81 rowCycle = true;
82 }
83 else
84 {
85 black = COLOR4D::BLACK;
86 white = black.Brightened( 0.15 );
87 rowCycle = false;
88 }
89 }
90 else
91 {
92 if( aBackground.GetBrightness() > 0.4 )
93 {
94 white = aBackground;
95 black = white.Darkened( 0.15 );
96 rowCycle = true;
97 }
98 else
99 {
100 black = COLOR4D::BLACK;
101 white = black.Brightened( 0.15 );
102 rowCycle = false;
103 }
104 }
105
106 for( int x = aRect.GetLeft(); x <= aRect.GetRight(); x += aCheckerboardSize.x )
107 {
108 bool colCycle = rowCycle;
109
110 for( int y = aRect.GetTop(); y <= aRect.GetBottom(); y += aCheckerboardSize.y )
111 {
112 wxColor bg = colCycle ? black.ToColour() : white.ToColour();
113
114 // Blend fg bg with the checkerboard
115 double alpha = (double)fg.Alpha() / 255.0;
116 unsigned char r = wxColor::AlphaBlend( fg.Red(), bg.Red(), alpha );
117 unsigned char g = wxColor::AlphaBlend( fg.Green(), bg.Green(), alpha );
118 unsigned char b = wxColor::AlphaBlend( fg.Blue(), bg.Blue(), alpha );
119
120 brush.SetColour( r, g, b );
121
122 aDC->SetBrush( brush );
123 aDC->DrawRectangle( x, y, aCheckerboardSize.x, aCheckerboardSize.y );
124
125 colCycle = !colCycle;
126 }
127
128 rowCycle = !rowCycle;
129 }
130
131 aDC->SetBrush( *wxWHITE_BRUSH );
132
133 if( aMargins[0] )
134 aDC->DrawRectangle( 0, 0, aMargins[0], aRect.GetHeight() );
135
136 if( aMargins[1] )
137 aDC->DrawRectangle( 0, 0, aRect.GetWidth(), aMargins[1] );
138
139 if( aMargins[2] )
140 aDC->DrawRectangle( aRect.GetWidth() - aMargins[2], 0, aMargins[2], aRect.GetHeight() );
141
142 if( aMargins[3] )
143 aDC->DrawRectangle( 0, aRect.GetHeight() - aMargins[3], aRect.GetWidth(), aMargins[3] );
144}
145
146
147COLOR_SWATCH::COLOR_SWATCH( wxWindow* aParent, const COLOR4D& aColor, int aID, const COLOR4D& aBackground,
148 const COLOR4D& aDefault, SWATCH_SIZE aSwatchSize, bool aTriggerWithSingleClick ) :
149 wxPanel( aParent, aID ),
150 m_color( aColor ),
151 m_background( aBackground ),
152 m_default( aDefault ),
153 m_userColors( nullptr ),
154 m_readOnly( false ),
155 m_supportsOpacity( true )
156{
157 wxASSERT_MSG( aSwatchSize != SWATCH_EXPAND,
158 wxS( "SWATCH_EXPAND not supported in COLOR_SWATCH" ) );
159
160 switch( aSwatchSize )
161 {
162 case SWATCH_MEDIUM: m_size = ConvertDialogToPixels( SWATCH_SIZE_MEDIUM_DU ); break;
163 case SWATCH_SMALL: m_size = ConvertDialogToPixels( SWATCH_SIZE_SMALL_DU ); break;
164 case SWATCH_LARGE: m_size = ConvertDialogToPixels( SWATCH_SIZE_LARGE_DU ); break;
165 case SWATCH_EXPAND: m_size = ConvertDialogToPixels( SWATCH_SIZE_LARGE_DU ); break;
166 }
167
168 m_checkerboardSize = ConvertDialogToPixels( CHECKERBOARD_SIZE_DU );
169 m_checkerboardBg = aParent->GetBackgroundColour();
170
171 auto sizer = new wxBoxSizer( wxHORIZONTAL );
172 SetSizer( sizer );
173
174 m_swatch = new wxStaticBitmap( this, aID, makeBitmap() );
175
176 sizer->Add( m_swatch, 0, 0 );
177
178 setupEvents( aTriggerWithSingleClick );
179}
180
181
182COLOR_SWATCH::COLOR_SWATCH( wxWindow* aParent, wxWindowID aID, const wxPoint& aPos, const wxSize& aSize,
183 long aStyle ) :
184 wxPanel( aParent, aID, aPos, aSize, aStyle ),
185 m_userColors( nullptr ),
186 m_readOnly( false ),
187 m_supportsOpacity( true )
188{
189 if( aSize == wxDefaultSize )
190 m_size = ConvertDialogToPixels( SWATCH_SIZE_MEDIUM_DU );
191 else
192 m_size = aSize;
193
194 m_checkerboardSize = ConvertDialogToPixels( CHECKERBOARD_SIZE_DU );
195 m_checkerboardBg = aParent->GetBackgroundColour();
196
197#ifdef __WXMAC__
198 // Adjust for border
199 m_size.x -= 2;
200 m_size.y -= 2;
201#endif
202
203 SetSize( m_size );
204
205 auto sizer = new wxBoxSizer( wxHORIZONTAL );
206 SetSizer( sizer );
207
208 m_swatch = new wxStaticBitmap( this, aID, makeBitmap() );
209
210 sizer->Add( m_swatch, 0, 0 );
211
212 setupEvents( false );
213}
214
215
216void COLOR_SWATCH::setupEvents( bool aTriggerWithSingleClick )
217{
218 if( dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
219 {
220 m_swatch->Bind( wxEVT_LEFT_DOWN, &COLOR_SWATCH::onMouseEvent, this );
221 }
222 else
223 {
224 // forward click to any other listeners, since we don't want them
225 m_swatch->Bind( wxEVT_LEFT_DOWN, &COLOR_SWATCH::rePostEvent, this );
226
227 // bind the events that trigger the dialog
228 m_swatch->Bind( wxEVT_LEFT_DCLICK, &COLOR_SWATCH::onMouseEvent, this );
229
230 if( aTriggerWithSingleClick )
231 {
232 m_swatch->Bind( wxEVT_LEFT_UP, &COLOR_SWATCH::onMouseEvent, this );
233 }
234 }
235
236 m_swatch->Bind( wxEVT_MIDDLE_DOWN, &COLOR_SWATCH::onMouseEvent, this );
237
238 m_swatch->Bind( wxEVT_RIGHT_DOWN, &COLOR_SWATCH::rePostEvent, this );
239}
240
241
242void COLOR_SWATCH::rePostEvent( wxEvent& aEvent )
243{
244 wxPostEvent( this, aEvent );
245}
246
247
249{
251}
252
253
254static void sendSwatchChangeEvent( COLOR_SWATCH& aSender )
255{
256 wxCommandEvent changeEvt( COLOR_SWATCH_CHANGED, aSender.GetId() );
257
258 // use this class as the object (alternative might be to
259 // set a custom event class but that's more work)
260 changeEvt.SetEventObject( &aSender );
261
262 wxPostEvent( &aSender, changeEvt );
263}
264
265
266void COLOR_SWATCH::SetSwatchColor( const COLOR4D& aColor, bool aSendEvent )
267{
268 m_color = aColor;
269
270 m_swatch->SetBitmap( makeBitmap() );
271
272 if( aSendEvent )
273 sendSwatchChangeEvent( *this );
274}
275
276
278{
279 m_default = aColor;
280}
281
282
284{
285 m_background = aBackground;
286
287 m_swatch->SetBitmap( makeBitmap() );
288}
289
290
292{
293 return m_color;
294}
295
296
298{
299 if( m_readOnly )
300 {
303
304 return;
305 }
306
307 DIALOG_COLOR_PICKER dialog( ::wxGetTopLevelParent( this ), m_color, m_supportsOpacity, m_userColors, m_default );
308
309 // ShowModal()'s event pump can let our owning panel rebuild or destroy us; guard `this`.
310 wxWeakRef<COLOR_SWATCH> self( this );
311 int result = dialog.ShowModal();
312
313 if( !self )
314 return;
315
316 if( result == wxID_OK )
317 {
318 COLOR4D newColor = dialog.GetColor();
319
320 m_color = newColor;
321 m_swatch->SetBitmap( makeBitmap() );
322 sendSwatchChangeEvent( *this );
323 }
324}
325
326
328{
329 wxWindow* parent = GetParent();
330 m_checkerboardBg = parent ? parent->GetBackgroundColour() : wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
331
332 if( m_swatch )
333 m_swatch->SetBitmap( makeBitmap() );
334}
static const COLOR4D WHITE
Definition color4d.h:401
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
static const COLOR4D BLACK
Definition color4d.h:402
A simple color swatch of the kind used to set layer colors.
void SetSwatchColor(const KIGFX::COLOR4D &aColor, bool aSendEvent)
Set the current swatch color directly.
wxSize m_checkerboardSize
KIGFX::COLOR4D m_checkerboardBg
KIGFX::COLOR4D m_default
bool m_supportsOpacity
If opacity is not supported the color chooser dialog will be displayed without it.
wxStaticBitmap * m_swatch
static void RenderToDC(wxDC *aDC, const KIGFX::COLOR4D &aColor, const KIGFX::COLOR4D &aBackground, const wxRect &aRect, const wxSize &aCheckerboardSize, const KIGFX::COLOR4D &aCheckerboardBackground, const std::vector< int > &aMargins={ 0, 0, 0, 0 })
void OnDarkModeToggle()
Respond to a change in the OS's DarkMode setting.
void GetNewSwatchColor()
Prompt for a new colour, using the colour picker dialog.
static wxBitmap MakeBitmap(const KIGFX::COLOR4D &aColor, const KIGFX::COLOR4D &aBackground, const wxSize &aSize, const wxSize &aCheckerboardSize, const KIGFX::COLOR4D &aCheckerboardBackground, const std::vector< int > &aMargins={ 0, 0, 0, 0 })
bool m_readOnly
A read-only swatch won't show the color chooser dialog but otherwise works normally.
KIGFX::COLOR4D GetSwatchColor() const
wxBitmap makeBitmap()
void onMouseEvent(wxEvent &aEvent)
Handle mouse events on the swatch, and trigger the color picker dialog if appropriate.
COLOR_SWATCH(wxWindow *aParent, const KIGFX::COLOR4D &aColor, int aID, const KIGFX::COLOR4D &aBackground, const KIGFX::COLOR4D &aDefault, SWATCH_SIZE aSwatchType, bool aTriggerWithSingleClick=false)
Construct a COLOR_SWATCH.
KIGFX::COLOR4D m_color
KIGFX::COLOR4D m_background
void setupEvents(bool aTriggerWithSingleClick)
std::function< void()> m_readOnlyCallback
void SetDefaultColor(const KIGFX::COLOR4D &aColor)
Sets the color that will be chosen with the "Reset to Default" button in the chooser.
void SetSwatchBackground(const KIGFX::COLOR4D &aBackground)
Set the swatch background color.
CUSTOM_COLORS_LIST * m_userColors
void rePostEvent(wxEvent &aEvent)
Pass unwanted events on to listeners of this object.
KIGFX::COLOR4D GetColor()
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:80
int ShowModal() override
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D Darkened(double aFactor) const
Return a color that is darker by a given factor, without modifying object.
Definition color4d.h:279
double GetBrightness() const
Returns the brightness value of the color ranged from 0.0 to 1.0.
Definition color4d.h:330
std::shared_ptr< wxString > m_text
Definition color4d.h:395
COLOR4D Brightened(double aFactor) const
Return a color that is brighter by a given factor, without modifying object.
Definition color4d.h:265
wxColour ToColour() const
Definition color4d.cpp:221
wxDEFINE_EVENT(COLOR_SWATCH_CHANGED, wxCommandEvent)
static void sendSwatchChangeEvent(COLOR_SWATCH &aSender)
static const wxSize SWATCH_SIZE_LARGE_DU(24, 16)
static const wxSize SWATCH_SIZE_MEDIUM_DU(24, 10)
static const wxSize CHECKERBOARD_SIZE_DU(3, 3)
static const wxSize SWATCH_SIZE_SMALL_DU(8, 6)
SWATCH_SIZE
@ SWATCH_MEDIUM
@ SWATCH_LARGE
@ SWATCH_EXPAND
@ SWATCH_SMALL
wxString result
Test unit parsing edge cases and error handling.