Files
2026-08-16 18:24:52 +07:00

771 lines
22 KiB
C++

// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cswitch.h"
#include "../cdrawcontext.h"
#include "../cbitmap.h"
#include "../cvstguitimer.h"
#include "../events.h"
#include "../algorithm.h"
namespace VSTGUI {
#if VSTGUI_ENABLE_DEPRECATED_METHODS
bool CSwitchBase::useLegacyIndexCalculation = false;
#endif
//------------------------------------------------------------------------
CSwitchBase::CSwitchBase (const CRect& size, IControlListener* listener, int32_t tag,
CBitmap* background)
: CControl (size, listener, tag, background)
{
setDefaultValue (0.f);
setWantsFocus (true);
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
CSwitchBase::CSwitchBase (const CRect& size, IControlListener* listener, int32_t tag,
int32_t subPixmaps, CCoord heightOfOneImage, int32_t iMaxPositions,
CBitmap* background, const CPoint& offset)
: CControl (size, listener, tag, background), offset (offset)
{
setNumSubPixmaps (subPixmaps);
setHeightOfOneImage (heightOfOneImage);
setDefaultValue (0.f);
setWantsFocus (true);
}
#endif
//------------------------------------------------------------------------
CSwitchBase::CSwitchBase (const CSwitchBase& other) : CControl (other)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
offset = other.offset;
setNumSubPixmaps (other.subPixmaps);
setHeightOfOneImage (other.heightOfOneImage);
#endif
setWantsFocus (true);
}
//------------------------------------------------------------------------
int32_t CSwitchBase::normalizedToIndex (float norm) const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return getMultiFrameBitmapIndex (*mfb, norm);
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
#include "../private/disabledeprecatedmessage.h"
if (useLegacyIndexCalculation)
#include "../private/enabledeprecatedmessage.h"
return static_cast<int32_t> (norm * (getNumSubPixmaps () - 1) + 0.5f);
return normalizedToSteps (norm, getNumSubPixmaps () - 1);
#else
return 0;
#endif
}
//------------------------------------------------------------------------
float CSwitchBase::indexToNormalized (int32_t index) const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return getNormValueFromMultiFrameBitmapIndex (*mfb, static_cast<uint16_t> (index));
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
return static_cast<float> (index) / static_cast<float> (getNumSubPixmaps () - 1);
#else
return 0.f;
#endif
}
//------------------------------------------------------------------------
void CSwitchBase::draw (CDrawContext* pContext)
{
if (auto bitmap = getDrawBackground ())
{
float norm = getValueNormalized ();
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (bitmap))
{
auto frameIndex = getMultiFrameBitmapIndex (*mfb, norm);
if (inverseBitmap)
frameIndex = getInverseIndex (*mfb, frameIndex);
mfb->drawFrame (pContext, frameIndex, getViewSize ().getTopLeft ());
}
else
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
if (inverseBitmap)
norm = 1.f - norm;
// source position in bitmap
CPoint where (0, heightOfOneImage * normalizedToIndex (norm));
bitmap->draw (pContext, getViewSize (), where);
#else
bitmap->draw (pContext, getViewSize ());
#endif
}
}
setDirty (false);
}
//------------------------------------------------------------------------
bool CSwitchBase::sizeToFit ()
{
if (auto bitmap = getDrawBackground ())
{
CRect vs (getViewSize ());
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (bitmap))
{
vs.setSize (mfb->getFrameSize ());
}
else
{
vs.setWidth (bitmap->getWidth ());
#if VSTGUI_ENABLE_DEPRECATED_METHODS
vs.setHeight (getHeightOfOneImage ());
#else
vs.setHeight (bitmap->getHeight ());
#endif
}
setViewSize (vs);
setMouseableArea (vs);
return true;
}
return false;
}
//------------------------------------------------------------------------
CMouseEventResult CSwitchBase::onMouseDown (CPoint& where, const CButtonState& buttons)
{
if (!(buttons & kLButton))
return kMouseEventNotHandled;
coef = calculateCoef ();
beginEdit ();
mouseStartValue = getValue ();
return onMouseMoved (where, buttons);
}
//------------------------------------------------------------------------
CMouseEventResult CSwitchBase::onMouseUp (CPoint& where, const CButtonState& buttons)
{
if (isEditing ())
endEdit ();
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult CSwitchBase::onMouseCancel ()
{
if (isEditing ())
{
value = mouseStartValue;
if (isDirty ())
{
valueChanged ();
invalid ();
}
endEdit ();
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult CSwitchBase::onMouseMoved (CPoint& where, const CButtonState& buttons)
{
if (isEditing ())
{
float norm = calcNormFromPoint (where);
if (inverseBitmap)
norm = 1.f - norm;
value = getMin () + norm * (getMax () - getMin ());
bounceValue ();
if (isDirty ())
{
valueChanged ();
invalid ();
}
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
void CSwitchBase::setInverseBitmap (bool state)
{
if (inverseBitmap != state)
{
inverseBitmap = state;
invalid ();
}
}
//------------------------------------------------------------------------
// CVerticalSwitch
//------------------------------------------------------------------------
/*! @class CVerticalSwitch
Define a switch with a given number of positions, the current position is defined by the position
of the last click on this object (the object is divided in its height by the number of position).
Each position has its subbitmap, each subbitmap is stacked in the given handle bitmap.
By clicking Alt+Left Mouse the default value is used.
Use a CMultiFrameBitmap for its background bitmap.
*/
//------------------------------------------------------------------------
/**
* CVerticalSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param background the switch bitmap
* @param offset unused
*/
//------------------------------------------------------------------------
CVerticalSwitch::CVerticalSwitch (const CRect& size, IControlListener* listener, int32_t tag,
CBitmap* background)
: CSwitchBase (size, listener, tag, background)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
heightOfOneImage = size.getHeight ();
setNumSubPixmaps (
background ? static_cast<int32_t> (background->getHeight () / heightOfOneImage) : 0);
#endif
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
/**
* CVerticalSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param subPixmaps number of sub bitmaps in background
* @param heightOfOneImage height of one sub bitmap
* @param iMaxPositions TODO
* @param background the switch bitmap
* @param offset unused
*/
//------------------------------------------------------------------------
CVerticalSwitch::CVerticalSwitch (const CRect& size, IControlListener* listener, int32_t tag,
int32_t subPixmaps, CCoord heightOfOneImage,
int32_t iMaxPositions, CBitmap* background, const CPoint& offset)
: CSwitchBase (size, listener, tag, subPixmaps, heightOfOneImage, iMaxPositions, background, offset)
{
}
#endif
//------------------------------------------------------------------------
CVerticalSwitch::CVerticalSwitch (const CVerticalSwitch& v)
: CSwitchBase (v)
{
}
//------------------------------------------------------------------------
double CVerticalSwitch::calculateCoef () const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return mfb->getFrameSize ().y / static_cast<double> (getMultiFrameBitmapRangeLength (*mfb));
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
return static_cast<double> (heightOfOneImage) / static_cast<double> (getNumSubPixmaps ());
#else
return 1.;
#endif
}
//------------------------------------------------------------------------
float CVerticalSwitch::calcNormFromPoint (const CPoint& where) const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return static_cast<int32_t> ((where.y - getViewSize ().top) / getCoef ()) /
static_cast<float> (getMultiFrameBitmapRangeLength (*mfb) - 1);
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
return static_cast<int32_t> ((where.y - getViewSize ().top) / getCoef ()) /
static_cast<float> (getNumSubPixmaps () - 1);
#else
return 0.f;
#endif
}
//------------------------------------------------------------------------
void CVerticalSwitch::onKeyboardEvent (KeyboardEvent& event)
{
if (event.type != EventType::KeyDown || event.modifiers.empty () == false)
return;
float norm = getValueNormalized ();
int32_t currentIndex = normalizedToIndex (norm);
if (event.virt == VirtualKey::Up && currentIndex > 0)
{
--currentIndex;
norm = indexToNormalized (currentIndex);
value = (getMax () - getMin ()) * norm + getMin ();
bounceValue ();
}
if (event.virt == VirtualKey::Down && norm < 1.f)
{
++currentIndex;
norm = indexToNormalized (currentIndex);
value = (getMax () - getMin ()) * norm + getMin ();
bounceValue ();
}
if (isDirty ())
{
invalid ();
beginEdit ();
valueChanged ();
endEdit ();
event.consumed = true;
}
}
//------------------------------------------------------------------------
// CHorizontalSwitch
//------------------------------------------------------------------------
/*! @class CHorizontalSwitch
Same as the CVerticalSwitch but horizontal.
Use a CMultiFrameBitmap for its background bitmap.
*/
//------------------------------------------------------------------------
/**
* CHorizontalSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param background the bitmap of the switch
* @param offset unused
*/
//------------------------------------------------------------------------
CHorizontalSwitch::CHorizontalSwitch (const CRect& size, IControlListener* listener, int32_t tag,
CBitmap* background)
: CSwitchBase (size, listener, tag, background)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
heightOfOneImage = size.getWidth ();
setNumSubPixmaps (background ? (int32_t) (background->getWidth () / heightOfOneImage) : 0);
#endif
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
/**
* CHorizontalSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param subPixmaps number of sub bitmaps in background
* @param heightOfOneImage height of one sub bitmap
* @param iMaxPositions ignored
* @param background the switch bitmap
* @param offset unused
*/
//------------------------------------------------------------------------
CHorizontalSwitch::CHorizontalSwitch (const CRect& size, IControlListener* listener, int32_t tag,
int32_t subPixmaps, CCoord heightOfOneImage,
int32_t iMaxPositions, CBitmap* background,
const CPoint& offset)
: CSwitchBase (size, listener, tag, subPixmaps, heightOfOneImage, iMaxPositions, background, offset)
{
}
#endif
//------------------------------------------------------------------------
CHorizontalSwitch::CHorizontalSwitch (const CHorizontalSwitch& v)
: CSwitchBase (v)
{
}
//------------------------------------------------------------------------
double CHorizontalSwitch::calculateCoef () const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return mfb->getFrameSize ().x / static_cast<double> (getMultiFrameBitmapRangeLength (*mfb));
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
return getDrawBackground ()->getWidth () / static_cast<double> (getNumSubPixmaps ());
#else
return 1.;
#endif
}
//------------------------------------------------------------------------
float CHorizontalSwitch::calcNormFromPoint (const CPoint& where) const
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (getDrawBackground ()))
{
return static_cast<int32_t> ((where.x - getViewSize ().left) / getCoef ()) /
static_cast<float> (getMultiFrameBitmapRangeLength (*mfb) - 1);
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
return static_cast<int32_t> ((where.x - getViewSize ().left) / getCoef ()) /
static_cast<float> (getNumSubPixmaps () - 1);
#else
return 0.f;
#endif
}
//------------------------------------------------------------------------
void CHorizontalSwitch::onKeyboardEvent(KeyboardEvent &event)
{
if (event.type != EventType::KeyDown || event.modifiers.empty () == false)
return;
float norm = getValueNormalized ();
int32_t currentIndex = normalizedToIndex (norm);
if (event.virt == VirtualKey::Left && currentIndex > 0)
{
--currentIndex;
norm = indexToNormalized (currentIndex);
value = (getMax () - getMin ()) * norm + getMin ();
bounceValue ();
}
if (event.virt == VirtualKey::Right && norm < 1.f)
{
++currentIndex;
norm = indexToNormalized (currentIndex);
value = (getMax () - getMin ()) * norm + getMin ();
bounceValue ();
}
if (isDirty ())
{
invalid ();
beginEdit ();
valueChanged ();
endEdit ();
event.consumed = true;
}
}
//------------------------------------------------------------------------
// CRockerSwitch
//------------------------------------------------------------------------
/*! @class CRockerSwitch
Define a rocker switch with 3 states using 3 subbitmaps.
One click on its leftside, then the first subbitmap is displayed.
One click on its rightside, then the third subbitmap is displayed.
When the mouse button is relaxed, the second subbitmap is framed. */
//------------------------------------------------------------------------
/**
* CRockerSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param background bitmap with 3 stacked images of the rocker switch
* @param style
*/
//------------------------------------------------------------------------
CRockerSwitch::CRockerSwitch (const CRect& size, IControlListener* listener, int32_t tag,
CBitmap* background, const int32_t style)
: CControl (size, listener, tag, background), style (style), resetValueTimer (nullptr)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
setNumSubPixmaps (3);
setHeightOfOneImage (size.getHeight ());
#endif
setWantsFocus (true);
setMin (-1.f);
setMax (1.f);
setValue ((getMax () - getMin ()) / 2.f + getMin ());
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
/**
* CRockerSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param background bitmap with 3 stacked images of the rocker switch
* @param offset
* @param style
*/
//------------------------------------------------------------------------
CRockerSwitch::CRockerSwitch (const CRect& size, IControlListener* listener, int32_t tag, CBitmap* background, const CPoint &offset, const int32_t style)
: CControl (size, listener, tag, background)
, offset (offset)
, style (style)
, resetValueTimer (nullptr)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
setNumSubPixmaps (3);
setHeightOfOneImage (size.getHeight ());
#endif
setWantsFocus (true);
setMin (-1.f);
setMax (1.f);
setValue ((getMax () - getMin ()) / 2.f + getMin ());
}
//------------------------------------------------------------------------
/**
* CRockerSwitch constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param heightOfOneImage height of one image in pixel
* @param background bitmap with 3 stacked images of the rocker switch
* @param offset
* @param style
*/
//------------------------------------------------------------------------
CRockerSwitch::CRockerSwitch (const CRect& size, IControlListener* listener, int32_t tag, CCoord heightOfOneImage, CBitmap* background, const CPoint &offset, const int32_t style)
: CControl (size, listener, tag, background)
, offset (offset)
, style (style)
, resetValueTimer (nullptr)
{
setNumSubPixmaps (3);
setHeightOfOneImage (heightOfOneImage);
setWantsFocus (true);
setMin (-1.f);
setMax (1.f);
setValue ((getMax () - getMin ()) / 2.f + getMin ());
}
#endif
//------------------------------------------------------------------------
CRockerSwitch::CRockerSwitch (const CRockerSwitch& v)
: CControl (v), style (v.style), resetValueTimer (nullptr)
{
#if VSTGUI_ENABLE_DEPRECATED_METHODS
offset = v.offset;
setHeightOfOneImage (v.heightOfOneImage);
#endif
setWantsFocus (true);
}
//------------------------------------------------------------------------
CRockerSwitch::~CRockerSwitch () noexcept
{
if (resetValueTimer)
resetValueTimer->forget ();
}
//------------------------------------------------------------------------
void CRockerSwitch::draw (CDrawContext *pContext)
{
if (auto bitmap = getDrawBackground ())
{
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (bitmap))
{
uint16_t frameIndex = getMultiFrameBitmapIndex (*mfb, getValueNormalized ());
mfb->drawFrame (pContext, frameIndex, getViewSize ().getTopLeft ());
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
CPoint where (offset.x, offset.y);
if (value == getMax ())
where.y += 2 * heightOfOneImage;
else if (value == (getMax () - getMin ()) / 2.f + getMin ())
where.y += heightOfOneImage;
bitmap->draw (pContext, getViewSize (), where);
#else
bitmap->draw (pContext, getViewSize ());
#endif
}
setDirty (false);
}
//------------------------------------------------------------------------
CMouseEventResult CRockerSwitch::onMouseDown (CPoint& where, const CButtonState& buttons)
{
if (!(buttons & kLButton))
return kMouseEventNotHandled;
mouseStartValue = value;
beginEdit ();
return onMouseMoved (where, buttons);
}
//------------------------------------------------------------------------
CMouseEventResult CRockerSwitch::onMouseUp (CPoint& where, const CButtonState& buttons)
{
if (isEditing ())
{
value = (getMax () - getMin ()) / 2.f + getMin ();
if (isDirty ())
invalid ();
endEdit ();
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult CRockerSwitch::onMouseCancel ()
{
if (isEditing ())
{
value = mouseStartValue;
if (isDirty ())
{
valueChanged ();
invalid ();
}
endEdit ();
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult CRockerSwitch::onMouseMoved (CPoint& where, const CButtonState& buttons)
{
if (isEditing ())
{
CCoord width_2 = getViewSize ().getWidth () / 2;
CCoord height_2 = getViewSize ().getHeight () / 2;
if (style & kHorizontal)
{
if (where.x >= getViewSize ().left && where.y >= getViewSize ().top &&
where.x <= (getViewSize ().left + width_2) && where.y <= getViewSize ().bottom)
value = getMin ();
else if (where.x >= (getViewSize ().left + width_2) && where.y >= getViewSize ().top &&
where.x <= getViewSize ().right && where.y <= getViewSize ().bottom)
value = getMax ();
else
value = mouseStartValue;
}
else
{
if (where.x >= getViewSize ().left && where.y >= getViewSize ().top &&
where.x <= getViewSize ().right && where.y <= (getViewSize ().top + height_2))
value = getMin ();
else if (where.x >= getViewSize ().left && where.y >= (getViewSize ().top + height_2) &&
where.x <= getViewSize ().right && where.y <= getViewSize ().bottom)
value = getMax ();
else
value = mouseStartValue;
}
if (isDirty ())
{
valueChanged ();
invalid ();
}
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
void CRockerSwitch::onKeyboardEvent (KeyboardEvent& event)
{
if (event.modifiers.empty () == false)
return;
if (event.type == EventType::KeyDown)
{
if (style & kHorizontal &&
(event.virt == VirtualKey::Left || event.virt == VirtualKey::Right))
{
value = event.virt == VirtualKey::Left ? getMin () : getMax ();
invalid ();
beginEdit ();
valueChanged ();
event.consumed = true;
}
if (style & kVertical && (event.virt == VirtualKey::Up || event.virt == VirtualKey::Down))
{
value = event.virt == VirtualKey::Up ? getMin () : getMax ();
invalid ();
beginEdit ();
valueChanged ();
event.consumed = true;
}
}
else if (event.type == EventType::KeyUp)
{
if ((style & kHorizontal &&
(event.virt == VirtualKey::Left || event.virt == VirtualKey::Right)) ||
(style & kVertical && (event.virt == VirtualKey::Up || event.virt == VirtualKey::Down)))
{
value = (getMax () - getMin ()) / 2.f + getMin ();
invalid ();
valueChanged ();
endEdit ();
event.consumed = true;
}
}
}
//------------------------------------------------------------------------
void CRockerSwitch::onMouseWheelEvent (MouseWheelEvent& event)
{
auto distance = event.deltaY;
if (distance == 0.)
return;
if (distance > 0)
value = getMin ();
else
value = getMax ();
if (isDirty ())
{
invalid ();
if (!isEditing ())
beginEdit ();
valueChanged ();
}
if (resetValueTimer == nullptr)
resetValueTimer = new CVSTGUITimer (this, 200);
resetValueTimer->stop ();
resetValueTimer->start ();
event.consumed = true;
}
//------------------------------------------------------------------------
CMessageResult CRockerSwitch::notify (CBaseObject* sender, IdStringPtr message)
{
if (sender == resetValueTimer)
{
float newValue = (getMax () - getMin ()) / 2.f + getMin ();
if (value != newValue)
{
value = newValue;
if (!isEditing ())
beginEdit ();
valueChanged ();
endEdit ();
setDirty (true);
}
resetValueTimer->forget ();
resetValueTimer = nullptr;
return kMessageNotified;
}
return CControl::notify (sender, message);
}
//-----------------------------------------------------------------------------------------------
bool CRockerSwitch::sizeToFit ()
{
if (auto bitmap = getDrawBackground ())
{
CRect vs (getViewSize ());
if (auto mfb = dynamic_cast<CMultiFrameBitmap*> (bitmap))
{
vs.setSize (mfb->getFrameSize ());
}
else
{
vs.setWidth (bitmap->getWidth ());
#if VSTGUI_ENABLE_DEPRECATED_METHODS
vs.setHeight (getHeightOfOneImage ());
#else
vs.setHeight (bitmap->getHeight ());
#endif
}
setViewSize (vs);
setMouseableArea (vs);
return true;
}
return false;
}
} // VSTGUI