KiCad PCB EDA Suite
Loading...
Searching...
No Matches
bitmap_button.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 (C) 2020 Ian McInerney <ian.s.mcinerney at ieee dot org>
5 * Copyright (C) 2020-2024 Kicad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <kiplatform/ui.h>
26#include <pgm_base.h>
29#include <wx/button.h>
30#include <wx/dcclient.h>
31#include <wx/renderer.h>
32#include <wx/settings.h>
33
34#define wxCONTROL_SEPARATOR wxCONTROL_SPECIAL
35
36
37BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
38 const wxSize& aSize, int aStyles ) :
39 wxPanel( aParent, aId, aPos, aSize, aStyles ),
40 m_isRadioButton( false ),
41 m_showBadge( false ),
42 m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red
43 m_badgeTextColor( wxColor( wxT( "white" ) ) ),
44 m_buttonState( 0 ),
45 m_padding( 0 ),
46 m_isToolbarButton( false ),
47 m_acceptDraggedInClicks( false ),
48 m_centerBitmap( true )
49{
50 m_badgeFont = GetFont().Smaller().MakeBold();
51
53}
54
55
56BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxBitmap& aDummyBitmap,
57 const wxPoint& aPos, const wxSize& aSize, int aStyles ) :
58 wxPanel( aParent, aId, aPos, aSize, aStyles ),
59 m_isRadioButton( false ),
60 m_showBadge( false ),
61 m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red
62 m_badgeTextColor( wxColor( wxT( "white" ) ) ),
63 m_buttonState( 0 ),
64 m_padding( 5 ),
65 m_isToolbarButton( false ),
66 m_acceptDraggedInClicks( false ),
67 m_centerBitmap( true )
68{
69 m_badgeFont = GetFont().Smaller().MakeBold();
70
72}
73
74
76{
77 Bind( wxEVT_DPI_CHANGED, &BITMAP_BUTTON::OnDPIChanged, this );
78 Bind( wxEVT_PAINT, &BITMAP_BUTTON::OnPaint, this );
79 Bind( wxEVT_LEFT_UP, &BITMAP_BUTTON::OnLeftButtonUp, this );
80 Bind( wxEVT_LEFT_DOWN, &BITMAP_BUTTON::OnLeftButtonDown, this );
81 Bind( wxEVT_LEAVE_WINDOW, &BITMAP_BUTTON::OnMouseLeave, this );
82 Bind( wxEVT_ENTER_WINDOW, &BITMAP_BUTTON::OnMouseEnter, this );
83 Bind( wxEVT_KILL_FOCUS, &BITMAP_BUTTON::OnKillFocus, this );
84 Bind( wxEVT_SET_FOCUS, &BITMAP_BUTTON::OnSetFocus, this );
85}
86
87
89{
90}
91
92
94{
96 return wxSize( m_unadjustedMinSize.x + m_padding * 2, wxButton::GetDefaultSize().y );
97
98 return m_unadjustedMinSize + wxSize( m_padding * 2, m_padding * 2 );
99}
100
101
103{
104 // Uncomment to override wxFB sizes
105 // SetMinSize( DoGetBestSize() );
106
107 InvalidateBestSize();
108}
109
110
111void BITMAP_BUTTON::SetPadding( int aPadding )
112{
113 m_padding = aPadding;
114
116}
117
118
119void BITMAP_BUTTON::SetBitmap( const wxBitmapBundle& aBmp )
120{
121 m_normalBitmap = aBmp;
122
123 // This is a bit of a hack, but fixes button scaling issues on some platforms when those buttons
124 // use KiScaledBitmap. When that method is retired, this can probably be revisited.
126 {
127 m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this );
128 }
129 else
130 {
131#ifndef __WXMSW__
132 m_unadjustedMinSize = m_normalBitmap.GetDefaultSize();
133#else
134 m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this );
135#endif
136 }
137
139}
140
141
142void BITMAP_BUTTON::SetDisabledBitmap( const wxBitmapBundle& aBmp )
143{
144 m_disabledBitmap = aBmp;
145}
146
147
148void BITMAP_BUTTON::AcceptDragInAsClick( bool aAcceptDragIn )
149{
150 m_acceptDraggedInClicks = aAcceptDragIn;
151}
152
153
154void BITMAP_BUTTON::OnMouseLeave( wxEvent& aEvent )
155{
156 if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED ) )
157 {
158 clearFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED );
159 Refresh();
160 }
161
162 aEvent.Skip();
163}
164
165
166void BITMAP_BUTTON::OnMouseEnter( wxEvent& aEvent )
167{
168 if( !hasFlag( wxCONTROL_CURRENT ) )
169 {
170 setFlag( wxCONTROL_CURRENT );
171 Refresh();
172 }
173
174 aEvent.Skip();
175}
176
177
178void BITMAP_BUTTON::OnKillFocus( wxEvent& aEvent )
179{
180 if( hasFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED ) )
181 {
182 clearFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED );
183 Refresh();
184 }
185
186 aEvent.Skip();
187}
188
189
190void BITMAP_BUTTON::OnSetFocus( wxEvent& aEvent )
191{
192 if( !hasFlag( wxCONTROL_CHECKABLE ) )
193 {
194 if( !hasFlag( wxCONTROL_FOCUSED ) )
195 {
196 setFlag( wxCONTROL_FOCUSED );
197 Refresh();
198 }
199 }
200
201 aEvent.Skip();
202}
203
204
205void BITMAP_BUTTON::OnLeftButtonUp( wxMouseEvent& aEvent )
206{
207 // Only create a button event when the control is enabled
208 // and only accept clicks that came without prior mouse-down if configured
209 if( !hasFlag( wxCONTROL_DISABLED )
210 && ( m_acceptDraggedInClicks || hasFlag( wxCONTROL_PRESSED | wxCONTROL_FOCUSED ) ) )
211 {
212 GetEventHandler()->CallAfter( [this]()
213 {
214 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
215 evt.SetEventObject( this );
216 GetEventHandler()->ProcessEvent( evt );
217 } );
218 }
219
220 clearFlag( wxCONTROL_PRESSED );
221 Refresh();
222
223 aEvent.Skip();
224}
225
226
227void BITMAP_BUTTON::OnLeftButtonDown( wxMouseEvent& aEvent )
228{
229 if( hasFlag( wxCONTROL_CHECKABLE ) )
230 {
231 if( hasFlag( wxCONTROL_CHECKED ) && !m_isRadioButton )
232 {
233 clearFlag( wxCONTROL_CHECKED );
234
235 GetEventHandler()->CallAfter(
236 [this]()
237 {
238 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
239 evt.SetEventObject( this );
240 evt.SetInt( 0 );
241 GetEventHandler()->ProcessEvent( evt );
242 } );
243 }
244 else
245 {
246 setFlag( wxCONTROL_CHECKED );
247
248 GetEventHandler()->CallAfter(
249 [this]()
250 {
251 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
252 evt.SetEventObject( this );
253 evt.SetInt( 1 );
254 GetEventHandler()->ProcessEvent( evt );
255 } );
256 }
257 }
258 else
259 {
260 setFlag( wxCONTROL_PRESSED );
261 }
262
263 Refresh();
264
265 aEvent.Skip();
266}
267
268
269void BITMAP_BUTTON::OnDPIChanged( wxDPIChangedEvent& aEvent )
270{
271 wxSize newBmSize = m_normalBitmap.GetPreferredBitmapSizeFor( this );
272
273 if( newBmSize != m_unadjustedMinSize )
274 {
275 m_unadjustedMinSize = newBmSize;
277 }
278
279 aEvent.Skip();
280}
281
282
283void BITMAP_BUTTON::OnPaint( wxPaintEvent& aEvent )
284{
285 bool darkMode = KIPLATFORM::UI::IsDarkTheme();
286 wxColor highlightColor = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
287
288 // The drawing rectangle
289 wxRect rect( wxPoint( 0, 0 ), GetSize() );
290 wxPaintDC dc( this );
291
293 {
294 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ) );
295 dc.DrawLine( wxPoint( GetSize().x / 2, 0 ), wxPoint( GetSize().x / 2, GetSize().y ) );
296 return;
297 }
298
299 // This drawing is done so the button looks the same as an AUI toolbar button
300 if( !hasFlag( wxCONTROL_DISABLED ) )
301 {
302 if( hasFlag( wxCONTROL_PRESSED ) )
303 {
304 dc.SetPen( wxPen( highlightColor ) );
305 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 20 : 150 ) ) );
306 dc.DrawRectangle( rect );
307 }
308 else if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_FOCUSED ) )
309 {
310 dc.SetPen( wxPen( highlightColor ) );
311 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) );
312
313 // Checked items need a lighter hover rectangle
314 if( hasFlag( wxCONTROL_CHECKED ) )
315 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 50 : 180 ) ) );
316
317 dc.DrawRectangle( rect );
318 }
319 else if( hasFlag( wxCONTROL_CHECKED ) )
320 {
321 dc.SetPen( wxPen( highlightColor ) );
322 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) );
323 dc.DrawRectangle( rect );
324 }
325 }
326
327 const wxBitmapBundle& bmp = hasFlag( wxCONTROL_DISABLED ) ? m_disabledBitmap : m_normalBitmap;
328
329 wxPoint drawBmpPos( m_padding, m_padding );
330 wxBitmap bmpImg;
332 wxSize bmSize;
333
335 {
337 bmSize = wxSize( size, size );
338 bmpImg = bmp.GetBitmap( bmSize * scale );
339
340 // wxBitmapBundle::GetBitmap thinks we need this rescaled to match the base size
341 if( bmpImg.IsOk() )
342 bmpImg.SetScaleFactor( scale );
343 }
344 else if( bmp.IsOk() )
345 {
346 bmpImg = bmp.GetBitmap( ToPhys( m_unadjustedMinSize ) );
347 bmSize = bmpImg.GetLogicalSize();
348 }
349
350 if( m_centerBitmap )
351 {
352 drawBmpPos.x = ( rect.width - bmSize.x ) / 2;
353 drawBmpPos.y = ( rect.height - bmSize.y ) / 2;
354 }
355
356 // Draw the bitmap with the upper-left corner offset by the padding
357 if( bmp.IsOk() )
358 dc.DrawBitmap( bmpImg, drawBmpPos, true );
359
360 // Draw the badge
361 if( m_showBadge )
362 {
363 dc.SetFont( m_badgeFont );
364
365 wxSize text_padding( 3, 1 );
366
367 if( m_padding )
368 text_padding *= 2;
369
370 wxSize box_size = dc.GetTextExtent( m_badgeText ) + text_padding;
371 wxSize box_offset = box_size;
372
373 if( m_padding != 0 )
374 box_offset += wxSize( m_padding / 3, m_padding / 3 );
375
376 dc.SetPen( wxPen( m_badgeColor ) );
377 dc.SetBrush( wxBrush( m_badgeColor ) );
378 dc.DrawRoundedRectangle( rect.GetRightBottom() - box_offset, box_size, -0.25 );
379
380 dc.SetTextForeground( m_badgeTextColor );
381 dc.DrawText( m_badgeText, rect.GetRightBottom() - box_offset + ( text_padding / 2 ) );
382 }
383}
384
385
386bool BITMAP_BUTTON::Enable( bool aEnable )
387{
388 // If the requested state is already the current state, don't do anything
389 if( aEnable != hasFlag( wxCONTROL_DISABLED ) )
390 return false;
391
392 wxPanel::Enable( aEnable );
393
394 if( aEnable && hasFlag( wxCONTROL_DISABLED ) )
395 {
396 clearFlag( wxCONTROL_DISABLED );
397 Refresh();
398 }
399
400 if( !aEnable && !hasFlag( wxCONTROL_DISABLED ) )
401 {
402 setFlag( wxCONTROL_DISABLED );
403 Refresh();
404 }
405
406 return true;
407}
408
409
411{
412 setFlag( wxCONTROL_CHECKABLE );
413}
414
415
417{
418 setFlag( wxCONTROL_CHECKABLE );
419 m_isRadioButton = true;
420}
421
422
424{
425 setFlag( wxCONTROL_SEPARATOR | wxCONTROL_DISABLED );
426
428}
429
430
431void BITMAP_BUTTON::Check( bool aCheck )
432{
433 wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) );
434
435 if( aCheck && !hasFlag( wxCONTROL_CHECKED ) )
436 {
437 setFlag( wxCONTROL_CHECKED );
438 Refresh();
439 }
440
441 if( !aCheck && hasFlag( wxCONTROL_CHECKED ) )
442 {
443 clearFlag( wxCONTROL_CHECKED );
444 Refresh();
445 }
446}
447
448
450{
451 wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) );
452
453 return hasFlag( wxCONTROL_CHECKED );
454}
#define wxCONTROL_SEPARATOR
void OnKillFocus(wxEvent &aEvent)
void SetIsRadioButton()
bool IsChecked() const
void OnSetFocus(wxEvent &aEvent)
void AcceptDragInAsClick(bool aAcceptDragIn=true)
Accept mouse-up as click even if mouse-down happened outside of the control.
wxFont m_badgeFont
void OnMouseLeave(wxEvent &aEvent)
bool Enable(bool aEnable=true) override
Enable the button.
void setFlag(int aFlag)
wxBitmapBundle m_disabledBitmap
void Check(bool aCheck=true)
Check the control.
bool hasFlag(int aFlag) const
void SetDisabledBitmap(const wxBitmapBundle &aBmp)
Set the bitmap shown when the button is disabled.
BITMAP_BUTTON(wxWindow *aParent, wxWindowID aId, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, int aStyles=wxBORDER_NONE|wxTAB_TRAVERSAL)
wxColor m_badgeTextColor
void OnLeftButtonDown(wxMouseEvent &aEvent)
wxString m_badgeText
void SetIsSeparator()
Render button as a toolbar separator.
void invalidateBestSize()
wxSize m_unadjustedMinSize
void clearFlag(int aFlag)
bool m_acceptDraggedInClicks
Draws bitmap centered in the control.
wxBitmapBundle m_normalBitmap
virtual wxSize DoGetBestSize() const override
void OnPaint(wxPaintEvent &aEvent)
void OnMouseEnter(wxEvent &aEvent)
void OnDPIChanged(wxDPIChangedEvent &aEvent)
void OnLeftButtonUp(wxMouseEvent &aEvent)
void SetIsCheckButton()
Setup the control as a two-state button (checked or unchecked).
void SetBitmap(const wxBitmapBundle &aBmp)
Set the bitmap shown when the button is enabled.
bool m_isToolbarButton
Accept mouse-up as click even if mouse-down happened outside of the control.
wxColor m_badgeColor
void SetPadding(int aPadding)
Set the amount of padding present on each side of the bitmap.
APPEARANCE m_Appearance
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition: pgm_base.cpp:678
double GetPixelScaleFactor(const wxWindow *aWindow)
Tries to determine the pixel scaling factor currently in use for the window.
Definition: wxgtk/ui.cpp:175
bool IsDarkTheme()
Determine if the desktop interface is currently using a dark theme or a light theme.
Definition: wxgtk/ui.cpp:48
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: pgm_base.cpp:1059
see class PGM_BASE
const int scale