/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2020 Ian McInerney * Copyright (C) 2020-2024 Kicad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #define wxCONTROL_SEPARATOR wxCONTROL_SPECIAL BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize, int aStyles ) : wxPanel( aParent, aId, aPos, aSize, aStyles ), m_isRadioButton( false ), m_showBadge( false ), m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red m_badgeTextColor( wxColor( wxT( "white" ) ) ), m_buttonState( 0 ), m_padding( 0 ), m_isToolbarButton( false ), m_acceptDraggedInClicks( false ), m_centerBitmap( false ) { m_badgeFont = GetFont().Smaller().MakeBold(); setupEvents(); } BITMAP_BUTTON::BITMAP_BUTTON( wxWindow* aParent, wxWindowID aId, const wxBitmap& aDummyBitmap, const wxPoint& aPos, const wxSize& aSize, int aStyles ) : wxPanel( aParent, aId, aPos, aSize, aStyles ), m_isRadioButton( false ), m_showBadge( false ), m_badgeColor( wxColor( 210, 0, 0 ) ), // dark red m_badgeTextColor( wxColor( wxT( "white" ) ) ), m_buttonState( 0 ), m_padding( 5 ), m_isToolbarButton( false ), m_acceptDraggedInClicks( false ), m_centerBitmap( false ) { m_badgeFont = GetFont().Smaller().MakeBold(); setupEvents(); } void BITMAP_BUTTON::setupEvents() { Bind( wxEVT_DPI_CHANGED, &BITMAP_BUTTON::OnDPIChanged, this ); Bind( wxEVT_PAINT, &BITMAP_BUTTON::OnPaint, this ); Bind( wxEVT_LEFT_UP, &BITMAP_BUTTON::OnLeftButtonUp, this ); Bind( wxEVT_LEFT_DOWN, &BITMAP_BUTTON::OnLeftButtonDown, this ); Bind( wxEVT_LEAVE_WINDOW, &BITMAP_BUTTON::OnMouseLeave, this ); Bind( wxEVT_ENTER_WINDOW, &BITMAP_BUTTON::OnMouseEnter, this ); Bind( wxEVT_KILL_FOCUS, &BITMAP_BUTTON::OnKillFocus, this ); Bind( wxEVT_SET_FOCUS, &BITMAP_BUTTON::OnSetFocus, this ); } BITMAP_BUTTON::~BITMAP_BUTTON() { } wxSize BITMAP_BUTTON::DoGetBestSize() const { return m_unadjustedMinSize + wxSize( m_padding * 2, m_padding * 2 ); } void BITMAP_BUTTON::SetPadding( int aPadding ) { m_padding = aPadding; InvalidateBestSize(); } void BITMAP_BUTTON::SetBitmap( const wxBitmapBundle& aBmp ) { m_normalBitmap = aBmp; // This is a bit of a hack, but fixes button scaling issues on some platforms when those buttons // use KiScaledBitmap. When that method is retired, this can probably be revisited. if( m_isToolbarButton ) { m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this ); } else { #ifndef __WXMSW__ m_unadjustedMinSize = m_normalBitmap.GetDefaultSize(); #else m_unadjustedMinSize = m_normalBitmap.GetPreferredBitmapSizeFor( this ); #endif } InvalidateBestSize(); } void BITMAP_BUTTON::SetDisabledBitmap( const wxBitmapBundle& aBmp ) { m_disabledBitmap = aBmp; } void BITMAP_BUTTON::AcceptDragInAsClick( bool aAcceptDragIn ) { m_acceptDraggedInClicks = aAcceptDragIn; } void BITMAP_BUTTON::OnMouseLeave( wxEvent& aEvent ) { if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED ) ) { clearFlag( wxCONTROL_CURRENT | wxCONTROL_PRESSED ); Refresh(); } aEvent.Skip(); } void BITMAP_BUTTON::OnMouseEnter( wxEvent& aEvent ) { if( !hasFlag( wxCONTROL_CURRENT ) ) { setFlag( wxCONTROL_CURRENT ); Refresh(); } aEvent.Skip(); } void BITMAP_BUTTON::OnKillFocus( wxEvent& aEvent ) { if( hasFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED ) ) { clearFlag( wxCONTROL_FOCUSED | wxCONTROL_CURRENT | wxCONTROL_PRESSED | wxCONTROL_SELECTED ); Refresh(); } aEvent.Skip(); } void BITMAP_BUTTON::OnSetFocus( wxEvent& aEvent ) { if( !hasFlag( wxCONTROL_CHECKABLE ) ) { if( !hasFlag( wxCONTROL_FOCUSED ) ) { setFlag( wxCONTROL_FOCUSED ); Refresh(); } } aEvent.Skip(); } void BITMAP_BUTTON::OnLeftButtonUp( wxMouseEvent& aEvent ) { // Only create a button event when the control is enabled // and only accept clicks that came without prior mouse-down if configured if( !hasFlag( wxCONTROL_DISABLED ) && ( m_acceptDraggedInClicks || hasFlag( wxCONTROL_PRESSED | wxCONTROL_FOCUSED ) ) ) { GetEventHandler()->CallAfter( [this]() { wxCommandEvent evt( wxEVT_BUTTON, GetId() ); evt.SetEventObject( this ); GetEventHandler()->ProcessEvent( evt ); } ); } clearFlag( wxCONTROL_PRESSED ); Refresh(); aEvent.Skip(); } void BITMAP_BUTTON::OnLeftButtonDown( wxMouseEvent& aEvent ) { if( hasFlag( wxCONTROL_CHECKABLE ) ) { if( hasFlag( wxCONTROL_CHECKED ) && !m_isRadioButton ) { clearFlag( wxCONTROL_CHECKED ); GetEventHandler()->CallAfter( [this]() { wxCommandEvent evt( wxEVT_BUTTON, GetId() ); evt.SetEventObject( this ); evt.SetInt( 0 ); GetEventHandler()->ProcessEvent( evt ); } ); } else { setFlag( wxCONTROL_CHECKED ); GetEventHandler()->CallAfter( [this]() { wxCommandEvent evt( wxEVT_BUTTON, GetId() ); evt.SetEventObject( this ); evt.SetInt( 1 ); GetEventHandler()->ProcessEvent( evt ); } ); } } else { setFlag( wxCONTROL_PRESSED ); } Refresh(); aEvent.Skip(); } void BITMAP_BUTTON::OnDPIChanged( wxDPIChangedEvent& aEvent ) { wxSize newBmSize = m_normalBitmap.GetPreferredBitmapSizeFor( this ); if( newBmSize != m_unadjustedMinSize ) { m_unadjustedMinSize = newBmSize; InvalidateBestSize(); } aEvent.Skip(); } void BITMAP_BUTTON::OnPaint( wxPaintEvent& aEvent ) { bool darkMode = KIPLATFORM::UI::IsDarkTheme(); wxColor highlightColor = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ); // The drawing rectangle wxRect rect( wxPoint( 0, 0 ), GetSize() ); wxPaintDC dc( this ); if( hasFlag( wxCONTROL_SEPARATOR ) ) { dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ) ); dc.DrawLine( wxPoint( GetSize().x / 2, 0 ), wxPoint( GetSize().x / 2, GetSize().y ) ); return; } // This drawing is done so the button looks the same as an AUI toolbar button if( !hasFlag( wxCONTROL_DISABLED ) ) { if( hasFlag( wxCONTROL_PRESSED ) ) { dc.SetPen( wxPen( highlightColor ) ); dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 20 : 150 ) ) ); dc.DrawRectangle( rect ); } else if( hasFlag( wxCONTROL_CURRENT | wxCONTROL_FOCUSED ) ) { dc.SetPen( wxPen( highlightColor ) ); dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) ); // Checked items need a lighter hover rectangle if( hasFlag( wxCONTROL_CHECKED ) ) dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 50 : 180 ) ) ); dc.DrawRectangle( rect ); } else if( hasFlag( wxCONTROL_CHECKED ) ) { dc.SetPen( wxPen( highlightColor ) ); dc.SetBrush( wxBrush( highlightColor.ChangeLightness( darkMode ? 40 : 170 ) ) ); dc.DrawRectangle( rect ); } } const wxBitmapBundle& bmp = hasFlag( wxCONTROL_DISABLED ) ? m_disabledBitmap : m_normalBitmap; wxPoint drawBmpPos( m_padding, m_padding ); wxBitmap bmpImg; double scale = KIPLATFORM::UI::GetPixelScaleFactor( this ); wxSize bmSize; if( m_isToolbarButton ) { int size = Pgm().GetCommonSettings()->m_Appearance.toolbar_icon_size; bmSize = wxSize( size, size ); bmpImg = bmp.GetBitmap( bmSize * scale ); // wxBitmapBundle::GetBitmap thinks we need this rescaled to match the base size if( bmpImg.IsOk() ) bmpImg.SetScaleFactor( scale ); } else if( bmp.IsOk() ) { bmpImg = bmp.GetBitmap( m_unadjustedMinSize ); bmSize = bmpImg.GetSize(); } if( m_centerBitmap ) { // dont let it go negative if bmp is larger than the button int x = std::max( ( rect.width - bmSize.x ) / 2, 0 ); int y = std::max( ( rect.height - bmSize.y ) / 2, 0 ); drawBmpPos = wxPoint( x, y ); } // Draw the bitmap with the upper-left corner offset by the padding if( bmp.IsOk() ) dc.DrawBitmap( bmpImg, drawBmpPos, true ); // Draw the badge if( m_showBadge ) { dc.SetFont( m_badgeFont ); wxSize text_padding( 3, 1 ); if( m_padding ) text_padding *= 2; wxSize box_size = dc.GetTextExtent( m_badgeText ) + text_padding; wxSize box_offset = box_size; if( m_padding != 0 ) box_offset += wxSize( m_padding / 3, m_padding / 3 ); dc.SetPen( wxPen( m_badgeColor ) ); dc.SetBrush( wxBrush( m_badgeColor ) ); dc.DrawRoundedRectangle( rect.GetRightBottom() - box_offset, box_size, -0.25 ); dc.SetTextForeground( m_badgeTextColor ); dc.DrawText( m_badgeText, rect.GetRightBottom() - box_offset + ( text_padding / 2 ) ); } } bool BITMAP_BUTTON::Enable( bool aEnable ) { // If the requested state is already the current state, don't do anything if( aEnable != hasFlag( wxCONTROL_DISABLED ) ) return false; wxPanel::Enable( aEnable ); if( aEnable && hasFlag( wxCONTROL_DISABLED ) ) { clearFlag( wxCONTROL_DISABLED ); Refresh(); } if( !aEnable && !hasFlag( wxCONTROL_DISABLED ) ) { setFlag( wxCONTROL_DISABLED ); Refresh(); } return true; } void BITMAP_BUTTON::SetIsCheckButton() { setFlag( wxCONTROL_CHECKABLE ); } void BITMAP_BUTTON::SetIsRadioButton() { setFlag( wxCONTROL_CHECKABLE ); m_isRadioButton = true; } void BITMAP_BUTTON::SetIsSeparator() { setFlag( wxCONTROL_SEPARATOR | wxCONTROL_DISABLED ); InvalidateBestSize(); } void BITMAP_BUTTON::Check( bool aCheck ) { wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) ); if( aCheck && !hasFlag( wxCONTROL_CHECKED ) ) { setFlag( wxCONTROL_CHECKED ); Refresh(); } if( !aCheck && hasFlag( wxCONTROL_CHECKED ) ) { clearFlag( wxCONTROL_CHECKED ); Refresh(); } } bool BITMAP_BUTTON::IsChecked() const { wxASSERT_MSG( hasFlag( wxCONTROL_CHECKABLE ), wxS( "Button is not a checkButton." ) ); return hasFlag( wxCONTROL_CHECKED ); }