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-2023 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( false )
49{
50 if( aSize == wxDefaultSize )
51 SetMinSize( wxButton::GetDefaultSize() + wxSize( m_padding * 2, m_padding * 2) );
52
53 m_badgeFont = GetFont().Smaller().MakeBold();
54
56}
57
58
59BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxBitmap& aDummyBitmap,
60 const wxPoint& aPos, const wxSize& aSize, int aStyles ) :
61 wxPanel( aParent, aId, aPos, aSize, aStyles ),
62 m_isRadioButton( false ),
63 m_showBadge( false ),
64 m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red
65 m_badgeTextColor( wxColor( wxT( "white" ) ) ),
66 m_buttonState( 0 ),
67 m_padding( 5 ),
68 m_isToolbarButton( false ),
69 m_acceptDraggedInClicks( false ),
70 m_centerBitmap( false )
71{
72 if( aSize == wxDefaultSize )
73 SetMinSize( wxButton::GetDefaultSize() + wxSize( m_padding * 2, m_padding * 2) );
74
75 m_badgeFont = GetFont().Smaller().MakeBold();
76
78}
79
80
82{
83 Bind( wxEVT_PAINT, &BITMAP_BUTTON::OnPaint, this );
84 Bind( wxEVT_LEFT_UP, &BITMAP_BUTTON::OnLeftButtonUp, this );
85 Bind( wxEVT_LEFT_DOWN, &BITMAP_BUTTON::OnLeftButtonDown, this );
86 Bind( wxEVT_LEAVE_WINDOW, &BITMAP_BUTTON::OnMouseLeave, this );
87 Bind( wxEVT_ENTER_WINDOW, &BITMAP_BUTTON::OnMouseEnter, this );
88 Bind( wxEVT_KILL_FOCUS, &BITMAP_BUTTON::OnKillFocus, this );
89 Bind( wxEVT_SET_FOCUS, &BITMAP_BUTTON::OnSetFocus, this );
90}
91
92
94{
95}
96
97
98void BITMAP_BUTTON::SetPadding( int aPadding )
99{
100 m_padding = aPadding;
101 SetMinSize( m_unadjustedMinSize + wxSize( aPadding * 2, aPadding * 2 ) );
102}
103
104
105void BITMAP_BUTTON::SetBitmap( const wxBitmapBundle& aBmp )
106{
107 m_normalBitmap = aBmp;
108
109 // This is a bit of a hack, but fixes button scaling issues on some platforms when those buttons
110 // use KiScaledBitmap. When that method is retired, this can probably be revisited.
112 {
113 m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this );
114 }
115 else
116 {
117#ifndef __WXMSW__
118 m_unadjustedMinSize = m_normalBitmap.GetDefaultSize();
119#else
120 m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this );
121#endif
122 }
123
124 SetMinSize( wxSize( m_unadjustedMinSize.GetWidth() + ( m_padding * 2 ),
125 m_unadjustedMinSize.GetHeight() + ( m_padding * 2 ) ) );
126}
127
128
129void BITMAP_BUTTON::SetDisabledBitmap( const wxBitmapBundle& aBmp )
130{
131 m_disabledBitmap = aBmp;
132}
133
134
135void BITMAP_BUTTON::AcceptDragInAsClick( bool aAcceptDragIn )
136{
137 m_acceptDraggedInClicks = aAcceptDragIn;
138}
139
140
141void BITMAP_BUTTON::OnMouseLeave( wxEvent& aEvent )
142{
143 if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED ) )
144 {
145 clearFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED );
146 Refresh();
147 }
148
149 aEvent.Skip();
150}
151
152
153void BITMAP_BUTTON::OnMouseEnter( wxEvent& aEvent )
154{
155 if( !hasFlag( wxCONTROL_CURRENT ) )
156 {
157 setFlag( wxCONTROL_CURRENT );
158 Refresh();
159 }
160
161 aEvent.Skip();
162}
163
164
165void BITMAP_BUTTON::OnKillFocus( wxEvent& aEvent )
166{
167 if( hasFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED ) )
168 {
169 clearFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED );
170 Refresh();
171 }
172
173 aEvent.Skip();
174}
175
176
177void BITMAP_BUTTON::OnSetFocus( wxEvent& aEvent )
178{
179 if( !hasFlag( wxCONTROL_CHECKABLE ) )
180 {
181 if( !hasFlag( wxCONTROL_FOCUSED ) )
182 {
183 setFlag( wxCONTROL_FOCUSED );
184 Refresh();
185 }
186 }
187
188 aEvent.Skip();
189}
190
191
192void BITMAP_BUTTON::OnLeftButtonUp( wxMouseEvent& aEvent )
193{
194 // Only create a button event when the control is enabled
195 // and only accept clicks that came without prior mouse-down if configured
196 if( !hasFlag( wxCONTROL_DISABLED )
197 && ( m_acceptDraggedInClicks || hasFlag( wxCONTROL_PRESSED | wxCONTROL_FOCUSED ) ) )
198 {
199 GetEventHandler()->CallAfter( [=]()
200 {
201 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
202 evt.SetEventObject( this );
203 GetEventHandler()->ProcessEvent( evt );
204 } );
205 }
206
207 clearFlag( wxCONTROL_PRESSED );
208 Refresh();
209
210 aEvent.Skip();
211}
212
213
214void BITMAP_BUTTON::OnLeftButtonDown( wxMouseEvent& aEvent )
215{
216 if( hasFlag( wxCONTROL_CHECKABLE ) )
217 {
218 if( hasFlag( wxCONTROL_CHECKED ) && !m_isRadioButton )
219 {
220 clearFlag( wxCONTROL_CHECKED );
221
222 GetEventHandler()->CallAfter(
223 [=]()
224 {
225 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
226 evt.SetEventObject( this );
227 evt.SetInt( 0 );
228 GetEventHandler()->ProcessEvent( evt );
229 } );
230 }
231 else
232 {
233 setFlag( wxCONTROL_CHECKED );
234
235 GetEventHandler()->CallAfter(
236 [=]()
237 {
238 wxCommandEvent evt( wxEVT_BUTTON, GetId() );
239 evt.SetEventObject( this );
240 evt.SetInt( 1 );
241 GetEventHandler()->ProcessEvent( evt );
242 } );
243 }
244 }
245 else
246 {
247 setFlag( wxCONTROL_PRESSED );
248 }
249
250 Refresh();
251
252 aEvent.Skip();
253}
254
255
256void BITMAP_BUTTON::OnPaint( wxPaintEvent& aEvent )
257{
258 bool darkMode = KIPLATFORM::UI::IsDarkTheme();
259 wxColor highlightColor = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
260
261 // The drawing rectangle
262 wxRect rect( wxPoint( 0, 0 ), GetSize() );
263 wxPaintDC dc( this );
264
266 {
267 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ) );
268 dc.DrawLine( wxPoint( GetSize().x / 2, 0 ), wxPoint( GetSize().x / 2, GetSize().y ) );
269 return;
270 }
271
272 // This drawing is done so the button looks the same as an AUI toolbar button
273 if( !hasFlag( wxCONTROL_DISABLED ) )
274 {
275 if( hasFlag( wxCONTROL_PRESSED ) )
276 {
277 dc.SetPen( wxPen( highlightColor ) );
278 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 20 : 150 ) ) );
279 dc.DrawRectangle( rect );
280 }
281 else if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_FOCUSED ) )
282 {
283 dc.SetPen( wxPen( highlightColor ) );
284 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) );
285
286 // Checked items need a lighter hover rectangle
287 if( hasFlag( wxCONTROL_CHECKED ) )
288 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 50 : 180 ) ) );
289
290 dc.DrawRectangle( rect );
291 }
292 else if( hasFlag( wxCONTROL_CHECKED ) )
293 {
294 dc.SetPen( wxPen( highlightColor ) );
295 dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) );
296 dc.DrawRectangle( rect );
297 }
298 }
299
300 const wxBitmapBundle& bmp = hasFlag( wxCONTROL_DISABLED ) ? m_disabledBitmap : m_normalBitmap;
301
302 wxPoint drawBmpPos( m_padding, m_padding );
303 wxBitmap bmpImg;
305 int size;
306 wxSize bmSize;
307
309 {
311 bmSize = wxSize( size, size );
312 bmpImg = bmp.GetBitmap( bmSize * scale );
313
314 // wxBitmapBundle::GetBitmap thinks we need this rescaled to match the base size
315 if( bmpImg.IsOk() )
316 bmpImg.SetScaleFactor( scale );
317 }
318 else if( bmp.IsOk() )
319 {
320 bmpImg = bmp.GetBitmapFor( this );
321 bmSize = bmpImg.GetSize();
322 }
323
324 if( m_centerBitmap )
325 {
326 // dont let it go negative if bmp is larger than the button
327 int x = std::max( ( rect.width - bmSize.x ) / 2, 0 );
328 int y = std::max( ( rect.height - bmSize.y ) / 2, 0 );
329 drawBmpPos = wxPoint( x, y );
330 }
331
332 // Draw the bitmap with the upper-left corner offset by the padding
333 if( bmp.IsOk() )
334 dc.DrawBitmap( bmpImg, drawBmpPos, true );
335
336 // Draw the badge
337 if( m_showBadge )
338 {
339 dc.SetFont( m_badgeFont );
340
341 wxSize text_padding( 3, 1 );
342
343 if( m_padding )
344 text_padding *= 2;
345
346 wxSize box_size = dc.GetTextExtent( m_badgeText ) + text_padding;
347 wxSize box_offset = box_size;
348
349 if( m_padding != 0 )
350 box_offset += wxSize( m_padding / 3, m_padding / 3 );
351
352 dc.SetPen( wxPen( m_badgeColor ) );
353 dc.SetBrush( wxBrush( m_badgeColor ) );
354 dc.DrawRoundedRectangle( rect.GetRightBottom() - box_offset, box_size, -0.25 );
355
356 dc.SetTextForeground( m_badgeTextColor );
357 dc.DrawText( m_badgeText, rect.GetRightBottom() - box_offset + ( text_padding / 2 ) );
358 }
359}
360
361
362bool BITMAP_BUTTON::Enable( bool aEnable )
363{
364 // If the requested state is already the current state, don't do anything
365 if( aEnable != hasFlag( wxCONTROL_DISABLED ) )
366 return false;
367
368 wxPanel::Enable( aEnable );
369
370 if( aEnable && hasFlag( wxCONTROL_DISABLED ) )
371 {
372 clearFlag( wxCONTROL_DISABLED );
373 Refresh();
374 }
375
376 if( !aEnable && !hasFlag( wxCONTROL_DISABLED ) )
377 {
378 setFlag( wxCONTROL_DISABLED );
379 Refresh();
380 }
381
382 return true;
383}
384
385
387{
388 setFlag( wxCONTROL_CHECKABLE );
389}
390
391
393{
394 setFlag( wxCONTROL_CHECKABLE );
395 m_isRadioButton = true;
396}
397
398
400{
401 setFlag( wxCONTROL_SEPARATOR | wxCONTROL_DISABLED );
402 SetMinSize( wxSize( m_padding * 2, wxButton::GetDefaultSize().y ) );
403}
404
405
406void BITMAP_BUTTON::Check( bool aCheck )
407{
408 wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) );
409
410 if( aCheck && !hasFlag( wxCONTROL_CHECKED ) )
411 {
412 setFlag( wxCONTROL_CHECKED );
413 Refresh();
414 }
415
416 if( !aCheck && hasFlag( wxCONTROL_CHECKED ) )
417 {
418 clearFlag( wxCONTROL_CHECKED );
419 Refresh();
420 }
421}
422
423
425{
426 wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) );
427
428 return hasFlag( wxCONTROL_CHECKED );
429}
#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.
wxSize m_unadjustedMinSize
void clearFlag(int aFlag)
bool m_acceptDraggedInClicks
Draws bitmap centered in the control.
wxBitmapBundle m_normalBitmap
void OnPaint(wxPaintEvent &aEvent)
void OnMouseEnter(wxEvent &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:650
double GetPixelScaleFactor(const wxWindow *aWindow)
Tries to determine the pixel scaling factor currently in use for the window.
Definition: gtk/ui.cpp:175
bool IsDarkTheme()
Determine if the desktop interface is currently using a dark theme or a light theme.
Definition: gtk/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:1031
see class PGM_BASE
const int scale