kicad-source/common/widgets/bitmap_button.cpp
Mark Roszko 50e2a12e17 Fix palette icon scaling on Windows, also extend KiBitmapBundle because we use bundles wrong
wxBitmapBundle assumes the smallest bitmap added is the intended "original" size for a particular use case.
And generally that is probably true. However, we re-use icons in some places where we intend for them to start at 16
or start at 24. This is problematic when GetPreferredBitmapSizeFor is called for calculations because it'll return the
16*scale number instead of 24*scale number. So let's just allow passing in a min height restriction to KiBitmapBundle
for when we reuse bitmaps.
2025-01-31 10:37:00 -05:00

463 lines
13 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( m_isToolbarButton )
{
int size = Pgm().GetCommonSettings()->m_Appearance.toolbar_icon_size;
bmSize = wxSize( size, size ) * scale;
bmpImg = bmp.GetBitmap( bmSize );
// 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( 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 );
}