kicad-source/common/widgets/bitmap_button.cpp
Marek Roszko 69a7dc95c5 Probably fix the bitmap button painting more
Testing on platforms shows we don't need this differentiated case between toolbar and non toolbar icons. Since we don't start with an small base bundle icon for the icons that probably had issues before.
2025-02-06 18:25:53 -05:00

453 lines
12 KiB
C++

/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2020 Ian McInerney <ian.s.mcinerney at ieee dot org>
* Copyright The 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 <kiplatform/ui.h>
#include <pgm_base.h>
#include <settings/common_settings.h>
#include <widgets/bitmap_button.h>
#include <wx/button.h>
#include <wx/dcclient.h>
#include <wx/renderer.h>
#include <wx/settings.h>
#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( true )
{
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( true )
{
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()
{
Unbind( wxEVT_DPI_CHANGED, &BITMAP_BUTTON::OnDPIChanged, this );
Unbind( wxEVT_PAINT, &BITMAP_BUTTON::OnPaint, this );
Unbind( wxEVT_LEFT_UP, &BITMAP_BUTTON::OnLeftButtonUp, this );
Unbind( wxEVT_LEFT_DOWN, &BITMAP_BUTTON::OnLeftButtonDown, this );
Unbind( wxEVT_LEAVE_WINDOW, &BITMAP_BUTTON::OnMouseLeave, this );
Unbind( wxEVT_ENTER_WINDOW, &BITMAP_BUTTON::OnMouseEnter, this );
Unbind( wxEVT_KILL_FOCUS, &BITMAP_BUTTON::OnKillFocus, this );
Unbind( wxEVT_SET_FOCUS, &BITMAP_BUTTON::OnSetFocus, this );
}
wxSize BITMAP_BUTTON::DoGetBestSize() const
{
if( hasFlag( wxCONTROL_SEPARATOR ) )
return wxSize( m_unadjustedMinSize.x + m_padding * 2, wxButton::GetDefaultSize().y );
return m_unadjustedMinSize + wxSize( m_padding * 2, m_padding * 2 );
}
void BITMAP_BUTTON::invalidateBestSize()
{
// Uncomment to override wxFB sizes
// SetMinSize( DoGetBestSize() );
InvalidateBestSize();
}
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::GetContentScaleFactor( this );
wxSize bmSize;
if( bmp.IsOk() )
{
bmpImg = bmp.GetBitmap( ToPhys( m_unadjustedMinSize ) );
bmSize = bmpImg.GetLogicalSize();
}
if( m_centerBitmap )
{
drawBmpPos.x = ( rect.width - bmSize.x ) / 2;
drawBmpPos.y = ( rect.height - bmSize.y ) / 2;
}
// 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 );
}