It sends a wxEVT_SWITCHING before it switches and a wxEVT_SWITCH after it switches, so you can stop the switch from happening. If there is a label, the control will take the width of the label, which might result in a wide switch. If this is undesired you'll have to make your own label instead of using the control's. I will try and fix this later.
You can change the color of the background, the button and the slider (both on and off states). There's also a global wxMixColours() functions, used when animating the switch.
SwitchCtrl.h:
Code: Select all
///////////////////////////////////////////////////////////////////////////////
// Name: SiwtchCtrl.h
// Purpose: Implementing a custom wxSwitchCtrl
// Author: AmadeusK525
// Created: 2020-04-13
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_SWITCHCTRL_H_
#define _WX_SWITCHCTRL_H_
#pragma once
#include <wx/control.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <wx/timer.h>
wxColour WXDLLIMPEXP_CORE wxMixColours(const wxColour& firstColour, const wxColour& secondColour, int percent);
class WXDLLIMPEXP_CORE wxSwitchCtrl : public wxControl
{
public:
wxSwitchCtrl() = default;
wxSwitchCtrl(wxWindow* parent,
wxWindowID id,
bool value = false,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxSize(30, 15),
long style = wxBORDER_NONE,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxControlNameStr);
virtual void SetLabel(const wxString& label) wxOVERRIDE;
virtual bool SetFont(const wxFont& font) wxOVERRIDE;
virtual bool SetForegroundColour(const wxColour& colour) wxOVERRIDE;
virtual wxSize DoGetBestClientSize() const wxOVERRIDE;
inline void Switch() { DoSwitch(!m_bIsOn, true); }
inline virtual void SetValue(bool state) { DoSwitch(state, false); }
inline bool GetValue() { return m_bIsOn; }
inline void SetEnabledColour(const wxColour& colour) { m_enabledColour = colour; }
inline void SetDisabledColour(const wxColour& colour) { m_disabledColour = colour; }
/*!
* \brief Set the number of units the buttons needs to travel when activaded.
* The higher the number, the slower the animation on activate/deactivate
* will be. Default is 10.
* \param units Number of units
*/
inline void SetUnitsToScroll(int units) { m_nUnitCount = units; }
wxDECLARE_EVENT_TABLE();
protected:
void DoSwitch(bool state, bool sendEvent = true);
void OnAnimationTimer(wxTimerEvent& event);
void OnPaint(wxPaintEvent& event);
void OnSize(wxSizeEvent& event);
void OnLeftDown(wxMouseEvent& event);
void OnLeftUp(wxMouseEvent& event);
void OnMouseMove(wxMouseEvent& event);
void OnMouseCaptureLost(wxMouseCaptureLostEvent& event);
wxStaticText* m_labelST = nullptr;
wxBoxSizer* m_sizer = nullptr;
wxColour m_enabledColour{ 50, 50, 255 };
wxColour m_disabledColour{ 100,100,100 };
wxColour m_buttonColour{ 30,30,30 };
bool m_bIsOn = false;
private:
int m_nCurrentUnit = 0;
int m_nCurrentButtonPos = 0;
int m_yOrigin = 0;
int m_nUnitCount = 10;
double m_radius = 2.0;
bool m_bWillDrag = false;
bool m_bIsDragging = false;
wxTimer m_tAnimationTimer;
wxSize m_szCacheSize;
};
wxDECLARE_EVENT(wxEVT_SWITCH, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_SWITCHING, wxCommandEvent);
#define EVT_SWITCH(winid, func) wx__DECLARE_EVT1(wxEVT_SWITCH, winid, wxCommandEventHandler(func))
#define EVT_SWITCHING(winid, func) wx__DECLARE_EVT1(wxEVT_SWITCHING, winid, wxCommandEventHandler(func))
#endif // _WX_SWITCHCTRL_H_
Code: Select all
#include "SwitchCtrl.h"
#include <wx/wx.h>
#include <wx/dcgraph.h>
wxColour wxMixColours(const wxColour& firstColour, const wxColour& secondColour, int percent)
{
int newRed = (double)((secondColour.Red() * percent) + (firstColour.Red() * (100 - percent))) / 100;
int newGreen = (double)((secondColour.Green() * percent) + (firstColour.Green() * (100 - percent))) / 100;
int newBlue = (double)((secondColour.Blue() * percent) + (firstColour.Blue() * (100 - percent))) / 100;
return wxColour((unsigned char)newRed, (unsigned char)newGreen, (unsigned char)newBlue);
}
// Implementing events
wxDEFINE_EVENT(wxEVT_SWITCH, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_SWITCHING, wxCommandEvent);
wxBEGIN_EVENT_TABLE(wxSwitchCtrl, wxControl)
EVT_PAINT(wxSwitchCtrl::OnPaint)
EVT_SIZE(wxSwitchCtrl::OnSize)
EVT_LEFT_DOWN(wxSwitchCtrl::OnLeftDown)
EVT_LEFT_UP(wxSwitchCtrl::OnLeftUp)
EVT_MOTION(wxSwitchCtrl::OnMouseMove)
EVT_MOUSE_CAPTURE_LOST(wxSwitchCtrl::OnMouseCaptureLost)
EVT_TIMER(12345, wxSwitchCtrl::OnAnimationTimer)
wxEND_EVENT_TABLE()
wxSwitchCtrl::wxSwitchCtrl(wxWindow* parent,
wxWindowID id,
bool value,
const wxString& label,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: wxControl(parent, id, pos, size, style, validator, name), m_tAnimationTimer(this, 12345)
{
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->AddStretchSpacer(1);
SetSizer(m_sizer);
if ( !label.IsEmpty() )
SetLabel(label);
SetCursor(wxCURSOR_CLOSED_HAND);
DoSwitch(value, false);
}
void wxSwitchCtrl::SetLabel(const wxString& label)
{
if ( !m_labelST )
{
m_labelST = new wxStaticText(this, -1, label, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL);
m_labelST->SetCursor(wxCURSOR_DEFAULT);
m_labelST->SetFont(GetFont());
m_labelST->SetForegroundColour(GetForegroundColour());
m_sizer->Insert(0, m_labelST, wxSizerFlags(0).Expand().Border(wxBOTTOM, 1));
m_sizer->AddSpacer(m_labelST->GetSize().y);
}
else
{
m_labelST->SetLabel(label);
}
wxControl::SetLabel(label);
Layout();
}
bool wxSwitchCtrl::SetFont(const wxFont& font)
{
if ( m_labelST )
m_labelST->SetFont(font);
wxControl::SetFont(font);
return true;
}
bool wxSwitchCtrl::SetForegroundColour(const wxColour& colour)
{
if ( m_labelST )
m_labelST->SetForegroundColour(colour);
wxControl::SetForegroundColour(colour);
return true;
}
wxSize wxSwitchCtrl::DoGetBestClientSize() const
{
if ( m_labelST )
{
wxSize labelSize = m_labelST->GetSize();
return labelSize + wxSize(labelSize.x < 15 ? 20 : labelSize.x, labelSize.y);
}
else
{
return FromDIP(wxSize(m_radius * 4, m_radius * 2));
}
}
void wxSwitchCtrl::DoSwitch(bool state, bool sendEvent)
{
if ( state == m_bIsOn )
sendEvent = false;
if ( sendEvent )
{
wxCommandEvent event(wxEVT_SWITCHING, GetId());
event.SetInt(state);
event.Skip();
ProcessEvent(event);
if ( !event.GetSkipped() )
return;
}
m_bIsOn = state;
m_szCacheSize = GetClientSize();
m_tAnimationTimer.Start(1);
if ( sendEvent )
{
wxCommandEvent* event = new wxCommandEvent(wxEVT_SWITCH, GetId());
event->SetInt(state);
QueueEvent(event);
}
}
void wxSwitchCtrl::OnAnimationTimer(wxTimerEvent& event)
{
if ( m_bIsOn )
{
if ( m_nCurrentUnit < m_nUnitCount )
{
m_nCurrentUnit++;
}
else
{
m_nCurrentUnit = m_nUnitCount;
m_tAnimationTimer.Stop();
}
}
else
{
if ( m_nCurrentUnit > 0 )
{
m_nCurrentUnit--;
}
else
{
m_nCurrentUnit = 0;
m_tAnimationTimer.Stop();
}
}
m_nCurrentButtonPos = ((double)(m_nCurrentUnit * (m_szCacheSize.x - (m_radius * 2))) / m_nUnitCount);
Refresh(true);
Update();
}
void wxSwitchCtrl::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
wxGCDC gdc(dc);
wxSize clientSize = GetClientSize();
wxColour slideColour = wxMixColours(m_disabledColour, m_enabledColour, (m_nCurrentUnit * 100) / m_nUnitCount);
gdc.SetBrush(wxBrush(slideColour));
gdc.SetPen(*wxTRANSPARENT_PEN);
gdc.DrawRoundedRectangle(wxPoint(0, m_yOrigin), clientSize - wxSize(0, m_yOrigin), m_radius);
gdc.SetBrush(wxBrush(m_buttonColour));
gdc.SetPen(*wxTRANSPARENT_PEN);
gdc.DrawCircle(wxPoint(m_radius + m_nCurrentButtonPos, clientSize.y - m_radius), m_radius - 1);
}
void wxSwitchCtrl::OnSize(wxSizeEvent& event)
{
wxSize size = event.GetSize();
if ( m_labelST )
{
wxSize labelSize = m_labelST->GetBestSize();
size.y -= labelSize.y;
m_yOrigin = labelSize.y;
if ( size.y < labelSize.y )
SetMinClientSize(wxSize(labelSize.x, labelSize.y * 2));
}
m_radius = (double)size.y / 2.0;
m_nCurrentButtonPos = ((double)(m_nCurrentUnit * (size.x - (m_radius * 2))) / m_nUnitCount);
event.Skip();
}
void wxSwitchCtrl::OnLeftDown(wxMouseEvent& event)
{
// If the user presses clicks on the button, prepare to
// drag, but don't change state to dragging yet.
int buttonWidth = m_radius * 2;
wxRect buttonRect(m_nCurrentButtonPos, GetClientSize().y - buttonWidth, buttonWidth, buttonWidth);
if ( buttonRect.Contains(event.GetPosition()) )
m_bWillDrag = true;
}
void wxSwitchCtrl::OnLeftUp(wxMouseEvent& event)
{
if ( !m_bIsDragging )
DoSwitch(!m_bIsOn);
else
{
DoSwitch(m_nCurrentUnit >= m_nUnitCount / 2 ? true : false);
ReleaseMouse();
}
m_bWillDrag = false;
m_bIsDragging = false;
}
void wxSwitchCtrl::OnMouseMove(wxMouseEvent& event)
{
if ( m_bWillDrag )
{
CaptureMouse();
m_bIsDragging = true;
m_bWillDrag = false;
}
if ( m_bIsDragging )
{
wxSize clientSize(GetClientSize());
wxPoint mousePos(event.GetPosition());
// Force input to be valid
if ( mousePos.x < 0 )
mousePos.x = 0;
else if ( mousePos.x > clientSize.x )
mousePos.x = clientSize.x;
int unitWidth = clientSize.x / m_nUnitCount;
// Calculate current button unit and its position
m_nCurrentUnit = mousePos.x / unitWidth;
m_nCurrentButtonPos = ((double)(m_nCurrentUnit * (clientSize.x - (m_radius * 2))) / m_nUnitCount);
// Update the screen for the user
Refresh();
Update();
}
event.Skip();
}
void wxSwitchCtrl::OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
{
m_bWillDrag = false;
m_bIsDragging = false;
DoSwitch(m_nCurrentUnit >= m_nUnitCount / 2 ? true : false);
}
Oh and there are event macros for it: EVT_SWITCH(winid, func) and EVT_SWITCHING(winid, func)