2260 lines
65 KiB
C++
2260 lines
65 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 "vst3editor.h"
|
|
#include "../vstgui.h"
|
|
#include "../lib/cvstguitimer.h"
|
|
#include "../lib/vstkeycode.h"
|
|
#include "../lib/animation/timingfunctions.h"
|
|
#include "../lib/animation/animations.h"
|
|
#include "../lib/platform/platformfactory.h"
|
|
#include "../uidescription/detail/uiviewcreatorattributes.h"
|
|
#include "../uidescription/editing/uieditcontroller.h"
|
|
#include "../uidescription/editing/uieditmenucontroller.h"
|
|
#include "../uidescription/uiattributes.h"
|
|
#include "../uidescription/uiviewfactory.h"
|
|
#include "../uidescription/cstream.h"
|
|
#include "base/source/fstring.h"
|
|
#include "base/source/updatehandler.h"
|
|
#include "pluginterfaces/base/keycodes.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <list>
|
|
#include <sstream>
|
|
|
|
#if LINUX
|
|
#include "../lib/platform/linux/x11frame.h"
|
|
#include "../lib/platform/linux/waylandframe.h"
|
|
#include "pluginterfaces/gui/iplugview.h"
|
|
#include "pluginterfaces/gui/iwaylandframe.h"
|
|
DEF_CLASS_IID (Steinberg::IWaylandFrame);
|
|
DEF_CLASS_IID (Steinberg::IWaylandHost);
|
|
#endif
|
|
|
|
#if defined(kVstVersionMajor) && defined(kVstVersionMinor)
|
|
#define VST3_SUPPORTS_CONTEXTMENU \
|
|
(kVstVersionMajor > 3 || (kVstVersionMajor == 3 && kVstVersionMinor > 1))
|
|
#if VST3_SUPPORTS_CONTEXTMENU
|
|
#include "pluginterfaces/vst/ivstcontextmenu.h"
|
|
#endif
|
|
#else
|
|
#define VST3_SUPPORTS_CONTEXTMENU 0
|
|
#endif
|
|
|
|
/// @cond ignore
|
|
namespace Steinberg {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class UpdateHandlerInit
|
|
{
|
|
public:
|
|
UpdateHandlerInit () { get (); }
|
|
|
|
UpdateHandler* get () { return UpdateHandler::instance (); }
|
|
};
|
|
|
|
static UpdateHandlerInit gUpdateHandlerInit;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class IdleUpdateHandler
|
|
{
|
|
public:
|
|
static void start ()
|
|
{
|
|
auto& instance = get ();
|
|
if (++instance.users == 1)
|
|
{
|
|
instance.timer = VSTGUI::makeOwned<VSTGUI::CVSTGUITimer> (
|
|
[] (VSTGUI::CVSTGUITimer*) { gUpdateHandlerInit.get ()->triggerDeferedUpdates (); },
|
|
1000 / 30);
|
|
}
|
|
}
|
|
|
|
static void stop ()
|
|
{
|
|
auto& instance = get ();
|
|
if (--instance.users == 0)
|
|
{
|
|
instance.timer = nullptr;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
static IdleUpdateHandler& get ()
|
|
{
|
|
static IdleUpdateHandler gInstance;
|
|
return gInstance;
|
|
}
|
|
|
|
VSTGUI::SharedPointer<VSTGUI::CVSTGUITimer> timer;
|
|
std::atomic<uint32_t> users {0};
|
|
};
|
|
|
|
} // namespace Steinberg
|
|
/// @endcond ignore
|
|
|
|
namespace VSTGUI {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class ParameterChangeListener : public Steinberg::FObject
|
|
{
|
|
public:
|
|
ParameterChangeListener (Steinberg::Vst::EditController* editController, Steinberg::Vst::Parameter* parameter, CControl* control)
|
|
: editController (editController)
|
|
, parameter (parameter)
|
|
{
|
|
if (parameter)
|
|
{
|
|
parameter->addRef ();
|
|
parameter->addDependent (this);
|
|
}
|
|
addControl (control);
|
|
if (parameter)
|
|
parameter->changed ();
|
|
}
|
|
|
|
~ParameterChangeListener () override
|
|
{
|
|
if (parameter)
|
|
{
|
|
parameter->removeDependent (this);
|
|
parameter->release ();
|
|
}
|
|
for (const auto& c : controls)
|
|
c->forget ();
|
|
}
|
|
|
|
void addControl (CControl* control)
|
|
{
|
|
if (containsControl (control))
|
|
return;
|
|
control->remember ();
|
|
controls.push_back (control);
|
|
Steinberg::Vst::ParamValue value = 0.;
|
|
if (parameter)
|
|
{
|
|
value = editController->getParamNormalized (getParameterID ());
|
|
}
|
|
else
|
|
{
|
|
if (auto ctrl = controls.front ())
|
|
value = ctrl->getValueNormalized ();
|
|
}
|
|
auto* display = dynamic_cast<CParamDisplay*> (control);
|
|
if (display)
|
|
display->setValueToStringFunction([this](float value, char utf8String[256], CParamDisplay* display) {
|
|
return convertValueToString (value, utf8String);
|
|
});
|
|
|
|
if (parameter)
|
|
parameter->deferUpdate ();
|
|
else
|
|
updateControlValue (value);
|
|
}
|
|
|
|
void removeControl (CControl* control)
|
|
{
|
|
for (const auto& c : controls)
|
|
{
|
|
if (c == control)
|
|
{
|
|
controls.remove (control);
|
|
control->forget ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool containsControl (CControl* control)
|
|
{
|
|
return std::find (controls.begin (), controls.end (), control) != controls.end ();
|
|
}
|
|
|
|
void PLUGIN_API update (FUnknown* changedUnknown, Steinberg::int32 message) override
|
|
{
|
|
if (message == IDependent::kChanged && parameter)
|
|
{
|
|
updateControlValue (editController->getParamNormalized (getParameterID ()));
|
|
}
|
|
}
|
|
|
|
Steinberg::Vst::ParamID getParameterID ()
|
|
{
|
|
if (parameter)
|
|
return parameter->getInfo ().id;
|
|
CControl* control = controls.front ();
|
|
if (control)
|
|
return static_cast<Steinberg::Vst::ParamID> (control->getTag ());
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
void beginEdit ()
|
|
{
|
|
if (parameter)
|
|
editController->beginEdit (getParameterID ());
|
|
}
|
|
|
|
void endEdit ()
|
|
{
|
|
if (parameter)
|
|
editController->endEdit (getParameterID ());
|
|
|
|
// fix textual representation.
|
|
// It can happen that a parameter edit does not change the normalized value but the textual
|
|
// representation shows a wrong text because the text is only translated when the
|
|
// normalized value changes.
|
|
Steinberg::Vst::String128 str {};
|
|
for (const auto& c : controls)
|
|
{
|
|
if (auto label = dynamic_cast<CTextLabel*> (c))
|
|
{
|
|
if (str[0] == 0)
|
|
{
|
|
editController->getParamStringByValue (
|
|
getParameterID (), editController->getParamNormalized (getParameterID ()),
|
|
str);
|
|
}
|
|
Steinberg::String s (str);
|
|
s.toMultiByte (Steinberg::kCP_Utf8);
|
|
if (label->getText () != s.text8 ())
|
|
label->setText (s.text8 ());
|
|
}
|
|
}
|
|
}
|
|
|
|
void performEdit (Steinberg::Vst::ParamValue value)
|
|
{
|
|
if (parameter)
|
|
{
|
|
auto id = getParameterID ();
|
|
if (editController->setParamNormalized (id, value) == Steinberg::kResultTrue)
|
|
editController->performEdit (id, editController->getParamNormalized (id));
|
|
}
|
|
else
|
|
{
|
|
updateControlValue (value);
|
|
}
|
|
}
|
|
Steinberg::Vst::Parameter* getParameter () const { return parameter; }
|
|
|
|
protected:
|
|
bool convertValueToString (float value, char utf8String[256])
|
|
{
|
|
if (parameter)
|
|
{
|
|
Steinberg::Vst::String128 utf16Str;
|
|
if (parameter && parameter->getInfo ().stepCount)
|
|
{
|
|
// convert back to normalized value
|
|
value = (float)editController->plainParamToNormalized (getParameterID (), (Steinberg::Vst::ParamValue)value);
|
|
}
|
|
editController->getParamStringByValue (getParameterID (), value, utf16Str);
|
|
Steinberg::String utf8Str (utf16Str);
|
|
utf8Str.toMultiByte (Steinberg::kCP_Utf8);
|
|
utf8Str.copyTo8 (utf8String, 0, 256);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void updateControlValue (Steinberg::Vst::ParamValue value)
|
|
{
|
|
bool mouseEnabled = true;
|
|
bool isStepCount = false;
|
|
Steinberg::Vst::ParamValue defaultValue = 0.5;
|
|
float minValue = 0.f;
|
|
float maxValue = 1.f;
|
|
if (parameter)
|
|
{
|
|
defaultValue = parameter->getInfo ().defaultNormalizedValue;
|
|
if (parameter->getInfo ().flags & Steinberg::Vst::ParameterInfo::kIsReadOnly)
|
|
mouseEnabled = false;
|
|
if (parameter->getInfo ().stepCount)
|
|
{
|
|
isStepCount = true;
|
|
value = parameter->toPlain (value);
|
|
defaultValue = parameter->toPlain (defaultValue);
|
|
minValue = (float)parameter->toPlain ((Steinberg::Vst::ParamValue)minValue);
|
|
maxValue = (float)parameter->toPlain ((Steinberg::Vst::ParamValue)maxValue);
|
|
}
|
|
}
|
|
for (const auto& c : controls)
|
|
{
|
|
c->setMouseEnabled (mouseEnabled);
|
|
if (parameter)
|
|
{
|
|
c->setDefaultValue ((float)defaultValue);
|
|
c->setMin (minValue);
|
|
c->setMax (maxValue);
|
|
}
|
|
auto* label = dynamic_cast<CTextLabel*>(c);
|
|
if (label)
|
|
{
|
|
Steinberg::Vst::ParamValue normValue = value;
|
|
if (isStepCount)
|
|
{
|
|
normValue = parameter->toNormalized (value);
|
|
}
|
|
Steinberg::Vst::String128 utf16Str;
|
|
if (editController->getParamStringByValue (getParameterID (), normValue, utf16Str) != Steinberg::kResultTrue)
|
|
continue;
|
|
Steinberg::String utf8Str (utf16Str);
|
|
utf8Str.toMultiByte (Steinberg::kCP_Utf8);
|
|
label->setText (utf8Str.text8 ());
|
|
}
|
|
else
|
|
{
|
|
if (isStepCount)
|
|
{
|
|
c->setMin (minValue);
|
|
c->setMax (maxValue);
|
|
|
|
auto getParamStringByIndex = [&] (int32_t i) {
|
|
Steinberg::Vst::String128 utf16Str;
|
|
editController->getParamStringByValue (
|
|
getParameterID (),
|
|
(Steinberg::Vst::ParamValue)i /
|
|
(Steinberg::Vst::ParamValue)parameter->getInfo ().stepCount,
|
|
utf16Str);
|
|
Steinberg::String utf8Str (utf16Str);
|
|
utf8Str.toMultiByte (Steinberg::kCP_Utf8);
|
|
return utf8Str;
|
|
};
|
|
|
|
if (auto optMenu = dynamic_cast<COptionMenu*> (c))
|
|
{
|
|
optMenu->removeAllEntry ();
|
|
for (Steinberg::int32 i = 0; i <= parameter->getInfo ().stepCount; i++)
|
|
optMenu->addEntry (getParamStringByIndex (i).text8 ());
|
|
c->setValue ((float)value - minValue);
|
|
}
|
|
else if (auto segmentButton = dynamic_cast<CSegmentButton*> (c))
|
|
{
|
|
segmentButton->removeAllSegments ();
|
|
for (Steinberg::int32 i = 0; i <= parameter->getInfo ().stepCount; i++)
|
|
{
|
|
CSegmentButton::Segment segment;
|
|
segment.name = getParamStringByIndex (i).text8 ();
|
|
segmentButton->addSegment (std::move (segment));
|
|
}
|
|
c->setValue ((float)value);
|
|
}
|
|
else
|
|
{
|
|
c->setValue ((float)value);
|
|
}
|
|
}
|
|
else
|
|
c->setValueNormalized ((float)value);
|
|
c->valueChanged ();
|
|
}
|
|
c->invalid ();
|
|
}
|
|
}
|
|
Steinberg::Vst::EditController* editController;
|
|
Steinberg::Vst::Parameter* parameter;
|
|
|
|
using ControlList = std::list<CControl*>;
|
|
ControlList controls;
|
|
};
|
|
|
|
namespace VST3EditorInternal {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static bool parseSize (const std::string& str, CPoint& point)
|
|
{
|
|
size_t sep = str.find (',', 0);
|
|
if (sep != std::string::npos)
|
|
{
|
|
point.x = strtol (str.c_str (), nullptr, 10);
|
|
point.y = strtol (str.c_str () + sep+1, nullptr, 10);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void releaseSubController (IController* subController)
|
|
{
|
|
if (auto ref = dynamic_cast<IReference*> (subController))
|
|
ref->forget();
|
|
else if (auto fobj = dynamic_cast<Steinberg::FObject*> (subController))
|
|
fobj->release ();
|
|
else
|
|
delete subController;
|
|
}
|
|
|
|
} // namespace VST3EditorInternal
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! @class VST3Editor
|
|
The VST3Editor class represents the view for a VST3 plug-in. It automatically binds the VST3 parameters to VSTGUI control tags and it includes an inline UI editor for rapid development.
|
|
@section setup Setup
|
|
Add the following code to your Steinberg::Vst::EditController class:
|
|
@code
|
|
IPlugView* PLUGIN_API MyEditController::createView (FIDString name)
|
|
{
|
|
if (strcmp (name, ViewType::kEditor) == 0)
|
|
{
|
|
return new VST3Editor (this, "view", "myEditor.uidesc");
|
|
}
|
|
return 0;
|
|
}
|
|
@endcode
|
|
To activate the inline editor you need to define the preprocessor definition "VSTGUI_LIVE_EDITING=1".
|
|
Rebuild your plug-in, start your prefered host, instanciate your plug-in, open the context menu inside your editor and choose "Enable Editing".
|
|
Now you can define tags, colors, fonts, bitmaps and add views to your editor.
|
|
|
|
See @ref page_uidescription_editor @n
|
|
*/
|
|
//-----------------------------------------------------------------------------
|
|
VST3Editor::VST3Editor (Steinberg::Vst::EditController* controller, UTF8StringPtr _viewName,
|
|
UTF8StringPtr _xmlFile)
|
|
: VSTGUIEditor (controller), delegate (dynamic_cast<IVST3EditorDelegate*> (controller))
|
|
{
|
|
description = new UIDescription (_xmlFile);
|
|
viewName = _viewName;
|
|
xmlFile = _xmlFile;
|
|
init ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
VST3Editor::VST3Editor (UIDescription* desc, Steinberg::Vst::EditController* controller,
|
|
UTF8StringPtr _viewName, UTF8StringPtr _xmlFile)
|
|
: VSTGUIEditor (controller), delegate (dynamic_cast<IVST3EditorDelegate*> (controller))
|
|
{
|
|
description = desc;
|
|
description->remember ();
|
|
viewName = _viewName;
|
|
if (_xmlFile)
|
|
xmlFile = _xmlFile;
|
|
init ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
VST3Editor::~VST3Editor ()
|
|
{
|
|
description->forget ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::queryInterface (const Steinberg::TUID iid, void** obj)
|
|
{
|
|
#ifdef VST3_CONTENT_SCALE_SUPPORT
|
|
QUERY_INTERFACE(iid, obj, Steinberg::IPlugViewContentScaleSupport::iid, Steinberg::IPlugViewContentScaleSupport)
|
|
#endif
|
|
QUERY_INTERFACE(iid, obj, Steinberg::Vst::IParameterFinder::iid, Steinberg::Vst::IParameterFinder)
|
|
return VSTGUIEditor::queryInterface (iid, obj);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::init ()
|
|
{
|
|
// we will always call CView::setDirty() on the main thread
|
|
VSTGUI::CView::kDirtyCallAlwaysOnMainThread = true;
|
|
|
|
setIdleRate (300);
|
|
if (description->parse ())
|
|
{
|
|
// get sizes
|
|
const auto* attr = description->getViewAttributes (viewName.c_str ());
|
|
if (attr)
|
|
{
|
|
const std::string* sizeStr = attr->getAttributeValue ("size");
|
|
const std::string* minSizeStr = attr->getAttributeValue ("minSize");
|
|
const std::string* maxSizeStr = attr->getAttributeValue ("maxSize");
|
|
if (sizeStr)
|
|
{
|
|
CPoint p;
|
|
if (VST3EditorInternal::parseSize (*sizeStr, p))
|
|
{
|
|
rect.right = (Steinberg::int32)p.x;
|
|
rect.bottom = (Steinberg::int32)p.y;
|
|
minSize = p;
|
|
maxSize = p;
|
|
}
|
|
}
|
|
if (minSizeStr)
|
|
VST3EditorInternal::parseSize (*minSizeStr, minSize);
|
|
if (maxSizeStr)
|
|
VST3EditorInternal::parseSize (*maxSizeStr, maxSize);
|
|
}
|
|
#if DEBUG
|
|
else
|
|
{
|
|
auto* debugAttr = new UIAttributes ();
|
|
debugAttr->setAttribute (UIViewCreator::kAttrClass, "CViewContainer");
|
|
debugAttr->setAttribute ("size", "300, 300");
|
|
description->addNewTemplate (viewName.c_str (), debugAttr);
|
|
rect.right = 300;
|
|
rect.bottom = 300;
|
|
minSize (rect.right, rect.bottom);
|
|
maxSize (rect.right, rect.bottom);
|
|
}
|
|
#endif
|
|
}
|
|
#if DEBUG
|
|
else
|
|
{
|
|
auto* attr = new UIAttributes ();
|
|
attr->setAttribute (UIViewCreator::kAttrClass, "CViewContainer");
|
|
attr->setAttribute ("size", "300, 300");
|
|
description->addNewTemplate (viewName.c_str (), attr);
|
|
rect.right = 300;
|
|
rect.bottom = 300;
|
|
minSize (rect.right, rect.bottom);
|
|
maxSize (rect.right, rect.bottom);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool VST3Editor::exchangeView (UTF8StringPtr newViewName)
|
|
{
|
|
if (viewName == newViewName)
|
|
return true;
|
|
|
|
const UIAttributes* attr = description->getViewAttributes (newViewName);
|
|
if (attr)
|
|
{
|
|
viewName = newViewName;
|
|
auto minSizeStr = attr->getAttributeValue ("minSize");
|
|
auto maxSizeStr = attr->getAttributeValue ("maxSize");
|
|
if (minSizeStr)
|
|
VST3EditorInternal::parseSize (*minSizeStr, minSize);
|
|
if (maxSizeStr)
|
|
VST3EditorInternal::parseSize (*maxSizeStr, maxSize);
|
|
requestRecreateView ();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::enableTooltips (bool state)
|
|
{
|
|
tooltipsEnabled = state;
|
|
if (getFrame ())
|
|
getFrame ()->enableTooltips (state);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool VST3Editor::setEditorSizeConstrains (const CPoint& newMinimumSize, const CPoint& newMaximumSize)
|
|
{
|
|
if (newMinimumSize.x <= newMaximumSize.x && newMinimumSize.y <= newMaximumSize.y)
|
|
{
|
|
minSize = newMinimumSize;
|
|
maxSize = newMaximumSize;
|
|
if (frame)
|
|
{
|
|
CRect currentSize, newSize;
|
|
getFrame ()->getSize (currentSize);
|
|
newSize = currentSize;
|
|
CCoord width = currentSize.getWidth ();
|
|
CCoord height = currentSize.getHeight ();
|
|
double scaleFactor = getAbsScaleFactor ();
|
|
if (width > maxSize.x * scaleFactor)
|
|
newSize.setWidth (maxSize.x * scaleFactor);
|
|
else if (width < minSize.x * scaleFactor)
|
|
newSize.setWidth (minSize.x * scaleFactor);
|
|
if (height > maxSize.y * scaleFactor)
|
|
newSize.setHeight (maxSize.y * scaleFactor);
|
|
else if (height < minSize.y * scaleFactor)
|
|
newSize.setHeight (minSize.y * scaleFactor);
|
|
if (newSize != currentSize)
|
|
requestResize (CPoint (newSize.getWidth (), newSize.getHeight ()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
double VST3Editor::getAbsScaleFactor () const
|
|
{
|
|
return getZoomFactor () * getContentScaleFactor ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
double VST3Editor::getContentScaleFactor () const { return contentScaleFactor; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
double VST3Editor::getZoomFactor () const { return zoomFactor; }
|
|
|
|
#ifdef VST3_CONTENT_SCALE_SUPPORT
|
|
//-----------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::setContentScaleFactor (ScaleFactor factor)
|
|
{
|
|
contentScaleFactor = factor;
|
|
if (getFrame ())
|
|
{
|
|
getFrame ()->setZoom (getAbsScaleFactor ());
|
|
}
|
|
return Steinberg::kResultOk;
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::setZoomFactor (double factor)
|
|
{
|
|
if (getZoomFactor () == factor)
|
|
return;
|
|
|
|
zoomFactor = factor;
|
|
|
|
if (getFrame () == nullptr)
|
|
return;
|
|
|
|
getFrame ()->setZoom (getAbsScaleFactor ());
|
|
|
|
if (delegate)
|
|
delegate->onZoomChanged (this, zoomFactor);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool VST3Editor::beforeSizeChange (const CRect& newSize, const CRect& oldSize)
|
|
{
|
|
if (sizeRequest)
|
|
return true;
|
|
sizeRequest = {newSize.getSize ()};
|
|
bool result = requestResize (*sizeRequest);
|
|
sizeRequest = {};
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool VST3Editor::requestResize (const CPoint& newSize)
|
|
{
|
|
if (!plugFrame)
|
|
return false;
|
|
|
|
Steinberg::ViewRect vr;
|
|
vr.right = static_cast<Steinberg::int32> (std::floor (newSize.x));
|
|
vr.bottom = static_cast<Steinberg::int32> (std::floor (newSize.y));
|
|
return plugFrame->resizeView (this, &vr) == Steinberg::kResultTrue ? true : false;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::getEditorSizeConstrains (CPoint& minimumSize, CPoint& maximumSize) const
|
|
{
|
|
minimumSize = minSize;
|
|
maximumSize = maxSize;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
ParameterChangeListener* VST3Editor::getParameterChangeListener (int32_t tag) const
|
|
{
|
|
if (tag != -1)
|
|
{
|
|
auto it = paramChangeListeners.find (tag);
|
|
if (it != paramChangeListeners.end ())
|
|
{
|
|
return it->second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::valueChanged (CControl* pControl)
|
|
{
|
|
using namespace Steinberg;
|
|
if (!pControl->isEditing ())
|
|
return;
|
|
|
|
ParameterChangeListener* pcl = getParameterChangeListener (pControl->getTag ());
|
|
if (pcl)
|
|
{
|
|
auto paramID = pcl->getParameterID ();
|
|
auto normalizedValue = static_cast<Vst::ParamValue> (pControl->getValueNormalized ());
|
|
auto* textEdit = dynamic_cast<CTextEdit*> (pControl);
|
|
if (textEdit && pcl->getParameter ())
|
|
{
|
|
Steinberg::String str (textEdit->getText ());
|
|
str.toWideString (kCP_Utf8);
|
|
if (getController ()->getParamValueByString (paramID,
|
|
const_cast<Vst::TChar*> (str.text16 ()),
|
|
normalizedValue) != kResultTrue)
|
|
{
|
|
pcl->update (nullptr, kChanged);
|
|
return;
|
|
}
|
|
}
|
|
pcl->performEdit (normalizedValue);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::beginEdit (int32_t index)
|
|
{
|
|
// we don't assume that every control tag is a parameter tag handled by this editor
|
|
// as sub classes could build custom CControlListeners for controls
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::endEdit (int32_t index)
|
|
{
|
|
// see above
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::controlBeginEdit (CControl* pControl)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (pControl->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->beginEdit ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::controlEndEdit (CControl* pControl)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (pControl->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->endEdit ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::controlTagWillChange (CControl* pControl)
|
|
{
|
|
if (pControl->getTag () != -1 && pControl->getListener () == this)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (pControl->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->removeControl (pControl);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::controlTagDidChange (CControl* pControl)
|
|
{
|
|
if (pControl->getTag () != -1 && pControl->getListener () == this)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (pControl->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->addControl (pControl);
|
|
}
|
|
else
|
|
{
|
|
Steinberg::Vst::EditController* editController = getController ();
|
|
if (editController)
|
|
{
|
|
Steinberg::Vst::Parameter* parameter = editController->getParameterObject (static_cast<Steinberg::Vst::ParamID> (pControl->getTag ()));
|
|
paramChangeListeners.insert (std::make_pair (pControl->getTag (), new ParameterChangeListener (editController, parameter, pControl)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::onViewAdded (CFrame* frame, CView* view)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::onViewRemoved (CFrame* frame, CView* view)
|
|
{
|
|
auto* control = dynamic_cast<CControl*> (view);
|
|
if (control && control->getTag () != -1)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (control->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->removeControl (control);
|
|
}
|
|
}
|
|
// TODO: Currently when in Edit Mode in UIEditor, subcontrollers will be released, even tho the view may be added again later on.
|
|
IController* controller = getViewController (view);
|
|
if (controller)
|
|
{
|
|
VST3EditorInternal::releaseSubController (controller);
|
|
view->removeAttribute (kCViewControllerAttribute);
|
|
}
|
|
}
|
|
|
|
#if VST3_SUPPORTS_CONTEXTMENU
|
|
/// @cond ignore
|
|
namespace VST3EditorInternal {
|
|
//-----------------------------------------------------------------------------
|
|
class ContextMenuTarget : public Steinberg::FObject, public Steinberg::Vst::IContextMenuTarget
|
|
{
|
|
public:
|
|
ContextMenuTarget (CCommandMenuItem* item) : item (item)
|
|
{
|
|
item->remember ();
|
|
}
|
|
~ContextMenuTarget () override
|
|
{
|
|
item->forget ();
|
|
}
|
|
|
|
Steinberg::tresult PLUGIN_API executeMenuItem (Steinberg::int32 tag) override
|
|
{
|
|
item->execute ();
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
|
|
OBJ_METHODS(ContextMenuTarget, Steinberg::FObject)
|
|
FUNKNOWN_METHODS(Steinberg::Vst::IContextMenuTarget, Steinberg::FObject)
|
|
protected:
|
|
CCommandMenuItem* item;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void addCOptionMenuEntriesToIContextMenu (VST3Editor* editor, COptionMenu* menu, Steinberg::Vst::IContextMenu* contextMenu)
|
|
{
|
|
for (CConstMenuItemIterator it = menu->getItems ()->begin (), end = menu->getItems ()->end (); it != end; ++it)
|
|
{
|
|
auto* commandItem = (*it).cast<CCommandMenuItem>();
|
|
if (commandItem)
|
|
commandItem->validate ();
|
|
|
|
Steinberg::Vst::IContextMenu::Item item = {};
|
|
Steinberg::String title ((*it)->getTitle ());
|
|
title.toWideString (Steinberg::kCP_Utf8);
|
|
title.copyTo16 (item.name, 0, 128);
|
|
if ((*it)->getSubmenu ())
|
|
{
|
|
item.flags = Steinberg::Vst::IContextMenu::Item::kIsGroupStart;
|
|
contextMenu->addItem (item, nullptr);
|
|
addCOptionMenuEntriesToIContextMenu (editor, (*it)->getSubmenu (), contextMenu);
|
|
item.flags = Steinberg::Vst::IContextMenu::Item::kIsGroupEnd;
|
|
contextMenu->addItem (item, nullptr);
|
|
}
|
|
else if ((*it)->isSeparator ())
|
|
{
|
|
item.flags = Steinberg::Vst::IContextMenu::Item::kIsSeparator;
|
|
contextMenu->addItem (item, nullptr);
|
|
}
|
|
else
|
|
{
|
|
if (commandItem)
|
|
{
|
|
if ((*it)->isChecked ())
|
|
item.flags |= Steinberg::Vst::IContextMenu::Item::kIsChecked;
|
|
if ((*it)->isEnabled () == false)
|
|
item.flags |= Steinberg::Vst::IContextMenu::Item::kIsDisabled;
|
|
auto* target = new ContextMenuTarget (commandItem);
|
|
contextMenu->addItem (item, target);
|
|
target->release ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
/// @endcond ignore
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::onMouseEvent (MouseEvent& event, CFrame* frame)
|
|
{
|
|
if (event.type != EventType::MouseDown)
|
|
return;
|
|
|
|
if (event.buttonState.isRight ())
|
|
{
|
|
COptionMenu* controllerMenu = (delegate && editingEnabled == false) ? delegate->createContextMenu (event.mousePosition, this) : nullptr;
|
|
if (allowedZoomFactors.empty () == false && editingEnabled == false)
|
|
{
|
|
if (controllerMenu == nullptr)
|
|
controllerMenu = new COptionMenu ();
|
|
else
|
|
controllerMenu->addSeparator ();
|
|
auto* zoomMenu = new COptionMenu ();
|
|
zoomMenu->setStyle (COptionMenu::kMultipleCheckStyle);
|
|
char zoomFactorString[128];
|
|
int32_t zoomFactorTag = 0;
|
|
for (std::vector<double>::const_iterator it = allowedZoomFactors.begin (), end = allowedZoomFactors.end (); it != end; ++it, ++zoomFactorTag)
|
|
{
|
|
snprintf (zoomFactorString, std::size (zoomFactorString), "%d%%",
|
|
static_cast<int> ((*it) * 100));
|
|
CMenuItem* item = zoomMenu->addEntry (new CCommandMenuItem (
|
|
{zoomFactorString, zoomFactorTag, this, "Zoom", zoomFactorString}));
|
|
if (getZoomFactor () == *it)
|
|
item->setChecked (true);
|
|
}
|
|
CMenuItem* item = controllerMenu->addEntry ("UI Zoom");
|
|
item->setSubmenu (zoomMenu);
|
|
}
|
|
#if VSTGUI_LIVE_EDITING
|
|
if (editingEnabled == false)
|
|
{
|
|
if (controllerMenu == nullptr)
|
|
controllerMenu = new COptionMenu ();
|
|
else
|
|
controllerMenu->addSeparator ();
|
|
CMenuItem* item = controllerMenu->addEntry (new CCommandMenuItem (
|
|
{"Open UIDescription Editor", this, "File", "Open UIDescription Editor"}));
|
|
item->setKey ("e", kControl);
|
|
item = controllerMenu->addEntry (new CCommandMenuItem (
|
|
{"Show 'Open UI Editor' Button", this, "File", "Show Editor Button"}));
|
|
if (enableShowEditButton ())
|
|
item->setChecked ();
|
|
item = controllerMenu->addEntry (new CCommandMenuItem (
|
|
{"Save Editor Screenshot", this, "File", "Save Editor Screenshot"}));
|
|
}
|
|
#endif
|
|
CViewContainer::ViewList views;
|
|
auto nonScaledPos = event.mousePosition;
|
|
frame->getTransform ().transform (nonScaledPos);
|
|
if (getFrame ()->getViewsAt (nonScaledPos, views, GetViewOptions ().deep ().includeViewContainer ()))
|
|
{
|
|
auto createOrPrepareMenu = [&] () {
|
|
if (controllerMenu == nullptr)
|
|
controllerMenu = new COptionMenu ();
|
|
else
|
|
controllerMenu->addSeparator ();
|
|
};
|
|
for (const auto& view : views)
|
|
{
|
|
auto viewController = getViewController (view);
|
|
if (!viewController)
|
|
continue;
|
|
if (auto ctrler = dynamic_cast<IContextMenuController2*> (viewController))
|
|
{
|
|
createOrPrepareMenu ();
|
|
ctrler->appendContextMenuItems (*controllerMenu, view, view->translateToLocal (nonScaledPos));
|
|
}
|
|
else if (auto contextMenuController = dynamic_cast<IContextMenuController*> (viewController))
|
|
{
|
|
createOrPrepareMenu ();
|
|
contextMenuController->appendContextMenuItems (*controllerMenu, view->translateToLocal (nonScaledPos));
|
|
}
|
|
}
|
|
}
|
|
#if VST3_SUPPORTS_CONTEXTMENU
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler3> handler (getController ()->getComponentHandler ());
|
|
Steinberg::Vst::ParamID paramID;
|
|
if (handler)
|
|
{
|
|
CPoint where2 (event.mousePosition);
|
|
getFrame ()->getTransform ().transform (where2);
|
|
bool paramFound = findParameter ((Steinberg::int32)where2.x, (Steinberg::int32)where2.y, paramID) == Steinberg::kResultTrue;
|
|
Steinberg::Vst::IContextMenu* contextMenu = handler->createContextMenu (this, paramFound ? ¶mID : nullptr);
|
|
if (contextMenu)
|
|
{
|
|
if (controllerMenu)
|
|
VST3EditorInternal::addCOptionMenuEntriesToIContextMenu (this, controllerMenu,
|
|
contextMenu);
|
|
getFrame ()->doAfterEventProcessing ([=] () {
|
|
contextMenu->popup (static_cast<Steinberg::UCoord> (where2.x),
|
|
static_cast<Steinberg::UCoord> (where2.y));
|
|
contextMenu->release ();
|
|
});
|
|
event.consumed = true;
|
|
}
|
|
}
|
|
if (!event.consumed)
|
|
{
|
|
#endif
|
|
if (controllerMenu && controllerMenu->getNbEntries () > 0)
|
|
{
|
|
controllerMenu->remember ();
|
|
SharedPointer<CFrame> blockFrame = getFrame ();
|
|
getFrame ()->doAfterEventProcessing ([=, mousePosition = event.mousePosition] () {
|
|
controllerMenu->setStyle (COptionMenu::kPopupStyle |
|
|
COptionMenu::kMultipleCheckStyle);
|
|
controllerMenu->popup (blockFrame, mousePosition);
|
|
controllerMenu->forget ();
|
|
});
|
|
event.consumed = true;
|
|
}
|
|
}
|
|
if (controllerMenu)
|
|
controllerMenu->forget ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::findParameter (Steinberg::int32 xPos, Steinberg::int32 yPos, Steinberg::Vst::ParamID& resultTag)
|
|
{
|
|
std::list<SharedPointer<CView> > views;
|
|
if (frame && getFrame ()->getViewsAt (CPoint (xPos, yPos), views))
|
|
{
|
|
CControl* control = nullptr;
|
|
std::list<SharedPointer<CView> >::const_iterator it = views.begin ();
|
|
while (it != views.end ())
|
|
{
|
|
control = (*it).cast<CControl>();
|
|
if (control)
|
|
{
|
|
if (control->getMouseEnabled () && control->getTag () != -1)
|
|
break;
|
|
control = nullptr;
|
|
if ((*it)->getTransparency () == false)
|
|
break;
|
|
}
|
|
it++;
|
|
}
|
|
if (control)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (control->getTag ());
|
|
if (pcl && pcl->containsControl (control) && pcl->getParameter ())
|
|
{
|
|
if (delegate && delegate->isPrivateParameter (pcl->getParameterID ()))
|
|
return Steinberg::kResultFalse;
|
|
resultTag = pcl->getParameterID ();
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
}
|
|
Steinberg::Vst::ParamID pid;
|
|
if (delegate && delegate->findParameter (CPoint (xPos, yPos), pid, this) && !delegate->isPrivateParameter (pid))
|
|
{
|
|
resultTag = pid;
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
}
|
|
return Steinberg::kResultFalse;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
IController* VST3Editor::createSubController (UTF8StringPtr name, const IUIDescription* desc)
|
|
{
|
|
return delegate ? delegate->createSubController (name, desc, this) : nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CView* VST3Editor::createView (const UIAttributes& attrs, const IUIDescription* desc)
|
|
{
|
|
if (delegate)
|
|
{
|
|
const std::string* customViewName = attrs.getAttributeValue (IUIDescription::kCustomViewName);
|
|
if (customViewName)
|
|
{
|
|
CView* view = delegate->createCustomView (customViewName->c_str (), attrs, desc, this);
|
|
return view;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CView* VST3Editor::verifyView (CView* view, const UIAttributes& attributes, const IUIDescription* desc)
|
|
{
|
|
if (delegate)
|
|
view = delegate->verifyView (view, attributes, desc, this);
|
|
auto* control = dynamic_cast<CControl*> (view);
|
|
if (control && control->getTag () != -1 && control->getListener () == this)
|
|
{
|
|
ParameterChangeListener* pcl = getParameterChangeListener (control->getTag ());
|
|
if (pcl)
|
|
{
|
|
pcl->addControl (control);
|
|
}
|
|
else
|
|
{
|
|
Steinberg::Vst::EditController* editController = getController ();
|
|
if (editController)
|
|
{
|
|
Steinberg::Vst::Parameter* parameter = editController->getParameterObject (static_cast<Steinberg::Vst::ParamID> (control->getTag ()));
|
|
paramChangeListeners.insert (std::make_pair (control->getTag (), new ParameterChangeListener (editController, parameter, control)));
|
|
}
|
|
}
|
|
}
|
|
return view;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void VST3Editor::recreateView ()
|
|
{
|
|
doCreateView = false;
|
|
enableEditing (editingEnabled);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::requestRecreateView ()
|
|
{
|
|
if (doCreateView || !frame)
|
|
return;
|
|
doCreateView = true;
|
|
auto task = [Self = Steinberg::IPtr<VST3Editor> (this)] () {
|
|
if (Self->frame)
|
|
Self->recreateView ();
|
|
};
|
|
if (frame->inEventProcessing ())
|
|
{
|
|
frame->doAfterEventProcessing (std::move (task));
|
|
}
|
|
else
|
|
{
|
|
task ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool VST3Editor::inEditMode () const { return editingEnabled; }
|
|
|
|
#if LINUX
|
|
// Map Steinberg Vst Interface to VSTGUI Interface
|
|
class RunLoop : public IRunLoop,
|
|
public AtomicReferenceCounted
|
|
{
|
|
public:
|
|
struct EventHandler : Steinberg::Linux::IEventHandler, public Steinberg::FObject
|
|
{
|
|
VSTGUI::IEventHandler* handler {nullptr};
|
|
|
|
void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor) override
|
|
{
|
|
if (handler)
|
|
handler->onEvent ();
|
|
}
|
|
DELEGATE_REFCOUNT (Steinberg::FObject)
|
|
DEFINE_INTERFACES
|
|
DEF_INTERFACE (Steinberg::Linux::IEventHandler)
|
|
END_DEFINE_INTERFACES (Steinberg::FObject)
|
|
};
|
|
struct TimerHandler : Steinberg::Linux::ITimerHandler, public Steinberg::FObject
|
|
{
|
|
VSTGUI::ITimerHandler* handler {nullptr};
|
|
|
|
void PLUGIN_API onTimer () final
|
|
{
|
|
if (handler)
|
|
handler->onTimer ();
|
|
}
|
|
DELEGATE_REFCOUNT (Steinberg::FObject)
|
|
DEFINE_INTERFACES
|
|
DEF_INTERFACE (Steinberg::Linux::ITimerHandler)
|
|
END_DEFINE_INTERFACES (Steinberg::FObject)
|
|
};
|
|
|
|
bool registerEventHandler (int fd, VSTGUI::IEventHandler* handler) final
|
|
{
|
|
if(!runLoop)
|
|
return false;
|
|
|
|
auto smtgHandler = Steinberg::owned (new EventHandler ());
|
|
smtgHandler->handler = handler;
|
|
if (runLoop->registerEventHandler (smtgHandler, fd) == Steinberg::kResultTrue)
|
|
{
|
|
eventHandlers.push_back (smtgHandler);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool unregisterEventHandler (VSTGUI::IEventHandler* handler) final
|
|
{
|
|
if(!runLoop)
|
|
return false;
|
|
|
|
for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it)
|
|
{
|
|
if ((*it)->handler == handler)
|
|
{
|
|
runLoop->unregisterEventHandler ((*it));
|
|
eventHandlers.erase (it);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool registerTimer (uint64_t interval, VSTGUI::ITimerHandler* handler) final
|
|
{
|
|
if(!runLoop)
|
|
return false;
|
|
|
|
auto smtgHandler = Steinberg::owned (new TimerHandler ());
|
|
smtgHandler->handler = handler;
|
|
if (runLoop->registerTimer (smtgHandler, interval) == Steinberg::kResultTrue)
|
|
{
|
|
timerHandlers.push_back (smtgHandler);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool unregisterTimer (VSTGUI::ITimerHandler* handler) final
|
|
{
|
|
if(!runLoop)
|
|
return false;
|
|
|
|
for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it)
|
|
{
|
|
if ((*it)->handler == handler)
|
|
{
|
|
runLoop->unregisterTimer ((*it));
|
|
timerHandlers.erase (it);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RunLoop (Steinberg::FUnknown* runLoop) : runLoop (runLoop) {}
|
|
private:
|
|
using EventHandlers = std::vector<Steinberg::IPtr<EventHandler>>;
|
|
using TimerHandlers = std::vector<Steinberg::IPtr<TimerHandler>>;
|
|
EventHandlers eventHandlers;
|
|
TimerHandlers timerHandlers;
|
|
Steinberg::FUnknownPtr<Steinberg::Linux::IRunLoop> runLoop;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class WaylandHost : public Wayland::IWaylandHost,
|
|
public AtomicReferenceCounted
|
|
{
|
|
public:
|
|
virtual wl_display* openWaylandConnection () final
|
|
{
|
|
if (!waylandHost)
|
|
return nullptr;
|
|
|
|
return waylandHost->openWaylandConnection ();
|
|
}
|
|
|
|
virtual bool closeWaylandConnection (wl_display* display) final
|
|
{
|
|
if (!waylandHost)
|
|
return false;
|
|
|
|
return waylandHost->closeWaylandConnection (display) == Steinberg::kResultOk;
|
|
}
|
|
|
|
WaylandHost (Steinberg::FUnknown* waylandHost) : waylandHost (waylandHost) {}
|
|
|
|
private:
|
|
Steinberg::FUnknownPtr<Steinberg::IWaylandHost> waylandHost;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class WaylandFrame : public Wayland::IWaylandFrame,
|
|
public AtomicReferenceCounted
|
|
{
|
|
public:
|
|
wl_surface* getWaylandSurface (wl_display* display) final
|
|
{
|
|
if (!waylandFrame)
|
|
return nullptr;
|
|
|
|
return waylandFrame->getWaylandSurface (display);
|
|
}
|
|
|
|
xdg_surface* getParentSurface (CRect& parentSize, wl_display* display) final
|
|
{
|
|
if (!waylandFrame)
|
|
return nullptr;
|
|
|
|
Steinberg::ViewRect viewRect;
|
|
xdg_surface* surface = waylandFrame->getParentSurface (viewRect, display);
|
|
parentSize = CRect (viewRect.left, viewRect.top, viewRect.right, viewRect.bottom);
|
|
return surface;
|
|
}
|
|
|
|
xdg_toplevel* getParentToplevel (wl_display* display) final
|
|
{
|
|
if (!waylandFrame)
|
|
return nullptr;
|
|
|
|
return waylandFrame->getParentToplevel (display);
|
|
}
|
|
|
|
WaylandFrame (Steinberg::FUnknown* waylandFrame) : waylandFrame (waylandFrame) {}
|
|
|
|
private:
|
|
Steinberg::FUnknownPtr<Steinberg::IWaylandFrame> waylandFrame;
|
|
};
|
|
#endif
|
|
|
|
#define kFrameEnableFocusDrawingAttr "frame-enable-focus-drawing"
|
|
#define kFrameFocusColorAttr "frame-focus-color"
|
|
#define kFrameFocusWidthAttr "frame-focus-width"
|
|
|
|
#if VSTGUI_LIVE_EDITING
|
|
// keyboard hook
|
|
struct VST3Editor::KeyboardHook : public IKeyboardHook
|
|
{
|
|
public:
|
|
using Func = std::function<void (KeyboardEvent& event, CFrame* frame)>;
|
|
|
|
KeyboardHook (Func&& keyDown, Func&& keyUp)
|
|
: onKeyDownFunc (std::move (keyDown)), onKeyUpFunc (std::move (keyUp))
|
|
{
|
|
}
|
|
|
|
private:
|
|
void onKeyboardEvent (KeyboardEvent& event, CFrame* frame) override
|
|
{
|
|
if (event.type == EventType::KeyDown)
|
|
{
|
|
onKeyDownFunc (event, frame);
|
|
}
|
|
else if (event.type == EventType::KeyUp)
|
|
{
|
|
onKeyUpFunc (event, frame);
|
|
}
|
|
}
|
|
|
|
Func onKeyDownFunc;
|
|
Func onKeyUpFunc;
|
|
};
|
|
#else
|
|
struct VST3Editor::KeyboardHook {};
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool PLUGIN_API VST3Editor::open (void* parent, const PlatformType& type)
|
|
{
|
|
frame = new CFrame (CRect (0, 0, 0, 0), this);
|
|
getFrame ()->setViewAddedRemovedObserver (this);
|
|
getFrame ()->setTransparency (true);
|
|
getFrame ()->registerMouseObserver (this);
|
|
#if VSTGUI_LIVE_EDITING
|
|
// will delete itself when the frame will be destroyed
|
|
keyboardHook = new KeyboardHook (
|
|
[this] (KeyboardEvent& event, CFrame* frame) {
|
|
if (event.modifiers.is (ModifierKey::Control) && frame->getModalView () == nullptr)
|
|
{
|
|
if (event.character == 'e')
|
|
{
|
|
editingEnabled = !editingEnabled;
|
|
requestRecreateView ();
|
|
event.consumed = true;
|
|
}
|
|
}
|
|
},
|
|
[] (KeyboardEvent&, CFrame*) { });
|
|
getFrame ()->registerKeyboardHook (keyboardHook);
|
|
#endif
|
|
getFrame ()->enableTooltips (tooltipsEnabled);
|
|
|
|
if (!enableEditing (false))
|
|
{
|
|
getFrame ()->forget ();
|
|
frame = nullptr;
|
|
return false;
|
|
}
|
|
|
|
IPlatformFrameConfig* config = nullptr;
|
|
#if LINUX
|
|
if (type == PlatformType::kWaylandSurfaceID)
|
|
{
|
|
if (parent != nullptr)
|
|
return false;
|
|
|
|
Wayland::FrameConfig* waylandConfig = new Wayland::FrameConfig;
|
|
waylandConfig->runLoop = owned (new RunLoop (plugFrame));
|
|
waylandConfig->waylandHost = owned (new WaylandHost (plugFrame));
|
|
waylandConfig->waylandFrame = owned (new WaylandFrame (plugFrame));
|
|
config = waylandConfig;
|
|
parent = config;
|
|
}
|
|
else
|
|
{
|
|
X11::FrameConfig* x11config = new X11::FrameConfig;
|
|
x11config->runLoop = owned (new RunLoop (plugFrame));
|
|
config = x11config;
|
|
}
|
|
#endif
|
|
|
|
getFrame ()->open (parent, type, config);
|
|
|
|
delete config;
|
|
config = nullptr;
|
|
|
|
if (delegate)
|
|
delegate->didOpen (this);
|
|
|
|
Steinberg::IdleUpdateHandler::start ();
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void PLUGIN_API VST3Editor::close ()
|
|
{
|
|
Steinberg::IdleUpdateHandler::stop ();
|
|
|
|
if (delegate)
|
|
delegate->willClose (this);
|
|
|
|
for (ParameterChangeListenerMap::const_iterator it = paramChangeListeners.begin (), end = paramChangeListeners.end (); it != end; ++it)
|
|
it->second->release ();
|
|
|
|
paramChangeListeners.clear ();
|
|
if (frame)
|
|
{
|
|
#if LINUX
|
|
wl_display* display {};
|
|
Steinberg::FUnknownPtr<Steinberg::IWaylandHost> host (controller->getHostContext ());
|
|
if (host)
|
|
{
|
|
if (!frame->getAttribute<wl_display*> ('WlDi', display))
|
|
{
|
|
display = nullptr;
|
|
}
|
|
}
|
|
|
|
#endif // LINUX
|
|
#if VSTGUI_LIVE_EDITING
|
|
if (keyboardHook)
|
|
{
|
|
getFrame ()->unregisterKeyboardHook (keyboardHook);
|
|
delete keyboardHook;
|
|
}
|
|
keyboardHook = nullptr;
|
|
openUIEditorController = nullptr;
|
|
#endif
|
|
getFrame ()->unregisterMouseObserver (this);
|
|
getFrame ()->removeAll (true);
|
|
int32_t refCount = getFrame ()->getNbReference ();
|
|
if (refCount == 1)
|
|
{
|
|
getFrame ()->close ();
|
|
frame = nullptr;
|
|
}
|
|
else
|
|
{
|
|
getFrame ()->forget ();
|
|
}
|
|
#if LINUX
|
|
if (host && display)
|
|
host->closeWaylandConnection (display);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::onSize (Steinberg::ViewRect* newSize)
|
|
{
|
|
if (sizeRequest)
|
|
{
|
|
auto width = static_cast<int32_t> (std::floor (sizeRequest->x));
|
|
auto height = static_cast<int32_t> (std::floor (sizeRequest->y));
|
|
if (width == newSize->getWidth () && height == newSize->getHeight ())
|
|
{
|
|
VSTGUIEditor::onSize (newSize);
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
return Steinberg::kResultFalse;
|
|
}
|
|
if (getFrame ())
|
|
{
|
|
CRect frameSize;
|
|
getFrame ()->getSize (frameSize);
|
|
auto width = static_cast<int32_t> (std::floor (frameSize.getWidth ()));
|
|
auto height = static_cast<int32_t> (std::floor (frameSize.getHeight ()));
|
|
if (frameSize.left == newSize->left && frameSize.top == newSize->top &&
|
|
width == newSize->getWidth () && height == newSize->getHeight ())
|
|
{
|
|
VSTGUIEditor::onSize (newSize);
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
}
|
|
sizeRequest = {CPoint (newSize->getWidth (), newSize->getHeight ())};
|
|
auto result = VSTGUIEditor::onSize (newSize);
|
|
sizeRequest = {};
|
|
return result;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::canResize ()
|
|
{
|
|
// always return true as this can change dynamicaly
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API VST3Editor::checkSizeConstraint (Steinberg::ViewRect* rect)
|
|
{
|
|
#if VSTGUI_LIVE_EDITING
|
|
if (editingEnabled)
|
|
return Steinberg::kResultTrue;
|
|
#endif
|
|
double scaleFactor = getAbsScaleFactor ();
|
|
CCoord width = rect->right - rect->left;
|
|
CCoord height = rect->bottom - rect->top;
|
|
if (width < minSize.x * scaleFactor)
|
|
width = minSize.x * scaleFactor;
|
|
else if (width > maxSize.x * scaleFactor)
|
|
width = maxSize.x * scaleFactor;
|
|
if (height < minSize.y * scaleFactor)
|
|
height = minSize.y * scaleFactor;
|
|
else if (height > maxSize.y * scaleFactor)
|
|
height = maxSize.y * scaleFactor;
|
|
if (width != rect->getWidth () || height != rect->getHeight ())
|
|
{
|
|
rect->right = static_cast<int32_t> (std::floor (width + rect->left));
|
|
rect->bottom = static_cast<int32_t> (std::floor (height + rect->top));
|
|
}
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool VST3Editor::validateCommandMenuItem (CCommandMenuItem* item)
|
|
{
|
|
#if VSTGUI_LIVE_EDITING
|
|
if (item->getCommandCategory () == "File")
|
|
{
|
|
if (item->getCommandName () == "Save")
|
|
{
|
|
bool enable = false;
|
|
UIAttributes* attributes = description->getCustomAttributes ("VST3Editor", true);
|
|
if (attributes)
|
|
{
|
|
const std::string* filePath = attributes->getAttributeValue ("Path");
|
|
if (filePath)
|
|
{
|
|
enable = true;
|
|
}
|
|
}
|
|
item->setEnabled (enable);
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool VST3Editor::onCommandMenuItemSelected (CCommandMenuItem* item)
|
|
{
|
|
auto& cmdCategory = item->getCommandCategory ();
|
|
#if VSTGUI_LIVE_EDITING
|
|
auto& cmdName = item->getCommandName ();
|
|
if (cmdCategory == "Edit")
|
|
{
|
|
if (cmdName == "Sync Parameter Tags")
|
|
{
|
|
syncParameterTags ();
|
|
return true;
|
|
}
|
|
}
|
|
else if (cmdCategory == "File")
|
|
{
|
|
if (cmdName == "Open UIDescription Editor")
|
|
{
|
|
editingEnabled = true;
|
|
requestRecreateView ();
|
|
return true;
|
|
}
|
|
else if (cmdName == "Close UIDescription Editor")
|
|
{
|
|
editingEnabled = false;
|
|
requestRecreateView ();
|
|
return true;
|
|
}
|
|
else if (cmdName == "Save")
|
|
{
|
|
save (false);
|
|
item->setChecked (false);
|
|
return true;
|
|
}
|
|
else if (cmdName == "Save As")
|
|
{
|
|
save (true);
|
|
item->setChecked (false);
|
|
return true;
|
|
}
|
|
else if (cmdName == "Save Editor Screenshot")
|
|
{
|
|
saveScreenshot ();
|
|
return true;
|
|
}
|
|
else if (cmdName == "Show Editor Button")
|
|
{
|
|
auto state = enableShowEditButton ();
|
|
enableShowEditButton (!state);
|
|
if (!editingEnabled)
|
|
showEditButton (!state);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
if (cmdCategory == "Zoom")
|
|
{
|
|
size_t index = static_cast<size_t> (item->getTag ());
|
|
if (index < allowedZoomFactors.size ())
|
|
{
|
|
setZoomFactor (allowedZoomFactors[index]);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
namespace VST3EditorInternal {
|
|
//------------------------------------------------------------------------
|
|
static int32_t getUIDescriptionSaveOptions (CFrame* frame)
|
|
{
|
|
int32_t flags = 0;
|
|
#if VSTGUI_LIVE_EDITING
|
|
auto* editController = dynamic_cast<UIEditController*> (getViewController (frame->getView (0)));
|
|
if (editController)
|
|
{
|
|
UIAttributes* attributes = editController->getSettings ();
|
|
bool val;
|
|
if (attributes->getBooleanAttribute (UIEditController::kEncodeBitmapsSettingsKey, val) && val == true)
|
|
{
|
|
#if ((VSTGUI_VERSION_MAJOR == 4 && VSTGUI_VERSION_MINOR > 9) || (VSTGUI_VERSION_MAJOR > 4))
|
|
flags |= UIDescription::kWriteImagesIntoUIDescFile;
|
|
#else
|
|
flags |= UIDescription::kWriteImagesIntoXMLFile;
|
|
#endif
|
|
}
|
|
if (attributes->getBooleanAttribute (UIEditController::kWriteWindowsRCFileSettingsKey, val) && val == true)
|
|
{
|
|
flags |= UIDescription::kWriteWindowsResourceFile;
|
|
}
|
|
}
|
|
#endif
|
|
return flags;
|
|
}
|
|
} // namespace VST3EditorInternal
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::save (bool saveAs)
|
|
{
|
|
UIAttributes* attributes = description->getCustomAttributes ("VST3Editor", true);
|
|
vstgui_assert(attributes);
|
|
if (!attributes)
|
|
return;
|
|
std::string savePath;
|
|
if (saveAs)
|
|
{
|
|
CNewFileSelector* fileSelector = CNewFileSelector::create (frame, CNewFileSelector::kSelectSaveFile);
|
|
if (fileSelector == nullptr)
|
|
return;
|
|
fileSelector->setTitle ("Save UIDescription File");
|
|
fileSelector->setDefaultExtension (CFileExtension ("VSTGUI UI Description", "uidesc"));
|
|
const std::string* prevFilePath = attributes->getAttributeValue ("Path");
|
|
if (prevFilePath)
|
|
fileSelector->setInitialDirectory (prevFilePath->c_str ());
|
|
else if (!xmlFile.empty ())
|
|
{
|
|
if (xmlFile[0] == '/')
|
|
fileSelector->setInitialDirectory (xmlFile.c_str ());
|
|
else
|
|
fileSelector->setDefaultSaveName (xmlFile.c_str ());
|
|
}
|
|
if (fileSelector->runModal ())
|
|
{
|
|
UTF8StringPtr filePath = fileSelector->getSelectedFile (0);
|
|
if (filePath)
|
|
{
|
|
attributes->setAttribute ("Path", filePath);
|
|
savePath = filePath;
|
|
}
|
|
}
|
|
fileSelector->forget ();
|
|
}
|
|
else
|
|
{
|
|
const std::string* filePath = attributes ? attributes->getAttributeValue ("Path") : nullptr;
|
|
if (filePath)
|
|
savePath = *filePath;
|
|
}
|
|
if (savePath.empty ())
|
|
return;
|
|
|
|
// filter out attributes we will always override with the values from the parameters
|
|
auto filter = [] (CView* view, const std::string& name) -> bool {
|
|
if (auto control = dynamic_cast<CControl*> (view))
|
|
{
|
|
if (control->getTag () != -1)
|
|
{
|
|
if (name == UIViewCreator::kAttrMinValue)
|
|
return false;
|
|
if (name == UIViewCreator::kAttrMaxValue)
|
|
return false;
|
|
if (name == UIViewCreator::kAttrDefaultValue)
|
|
return false;
|
|
if (name == UIViewCreator::kAttrMouseEnabled)
|
|
return false;
|
|
if (name == UIViewCreator::kAttrTitle && dynamic_cast<CTextLabel*> (control))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (description->save (savePath.c_str (),
|
|
VST3EditorInternal::getUIDescriptionSaveOptions (frame), filter))
|
|
description->setFilePath (savePath.c_str ());
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::syncParameterTags ()
|
|
{
|
|
#if VSTGUI_LIVE_EDITING
|
|
CView* view = getFrame ()->getView (0);
|
|
if (view)
|
|
{
|
|
IController* controller = getViewController (view);
|
|
IActionPerformer* actionPerformer = controller ? dynamic_cast<IActionPerformer*>(controller) : nullptr;
|
|
if (actionPerformer)
|
|
{
|
|
Steinberg::Vst::EditController* editController = getController ();
|
|
|
|
actionPerformer->beginGroupAction ("Sync Parameter Tags");
|
|
|
|
std::map<Steinberg::Vst::UnitID, Steinberg::Vst::UnitInfo> units;
|
|
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitInfo> ec2 (editController->unknownCast ());
|
|
if (ec2)
|
|
{
|
|
Steinberg::int32 unitCount = ec2->getUnitCount ();
|
|
Steinberg::Vst::UnitInfo info;
|
|
for (int32_t i = 0; i < unitCount; i++)
|
|
{
|
|
ec2->getUnitInfo (i, info);
|
|
units.insert (std::pair<Steinberg::Vst::UnitID, Steinberg::Vst::UnitInfo> (info.id, info));
|
|
}
|
|
}
|
|
|
|
int32_t paramCount = editController->getParameterCount ();
|
|
for (int32_t i = 0; i < paramCount; i++)
|
|
{
|
|
Steinberg::Vst::ParameterInfo info;
|
|
if (editController->getParameterInfo (i, info) == Steinberg::kResultTrue)
|
|
{
|
|
Steinberg::String paramTitle (info.title);
|
|
if (info.unitId != Steinberg::Vst::kRootUnitId)
|
|
{
|
|
std::map<Steinberg::Vst::UnitID, Steinberg::Vst::UnitInfo>::const_iterator it = units.find (info.unitId);
|
|
if (it != units.end ())
|
|
{
|
|
paramTitle.insertAt (0, "::");
|
|
paramTitle.insertAt (0, it->second.name);
|
|
}
|
|
}
|
|
else if (!units.empty ())
|
|
{
|
|
paramTitle.insertAt (0, "::");
|
|
paramTitle.insertAt (0, "Root");
|
|
}
|
|
paramTitle.toMultiByte (Steinberg::kCP_Utf8);
|
|
paramTitle.removeChars (' ');
|
|
Steinberg::String paramIDStr;
|
|
paramIDStr.printInt64 (info.id);
|
|
if (int32_t tag = description->getTagForName (paramTitle) != -1)
|
|
{
|
|
if (tag != info.id)
|
|
actionPerformer->performTagChange (paramTitle, paramIDStr);
|
|
}
|
|
else if (UTF8StringPtr tagName = description->lookupControlTagName (static_cast<int32_t> (info.id)))
|
|
{
|
|
actionPerformer->performTagNameChange (tagName, paramTitle);
|
|
}
|
|
else
|
|
{
|
|
actionPerformer->performTagChange (paramTitle, paramIDStr);
|
|
}
|
|
}
|
|
}
|
|
actionPerformer->finishGroupAction ();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::saveScreenshot ()
|
|
{
|
|
if (auto fileSelector =
|
|
owned (CNewFileSelector::create (getFrame (), CNewFileSelector::kSelectDirectory)))
|
|
{
|
|
fileSelector->setTitle ("Select Directory where to save the screenshots");
|
|
fileSelector->run ([this] (CNewFileSelector* fs) {
|
|
if (fs->getNumSelectedFiles () != 1)
|
|
return;
|
|
|
|
auto makeScreenshot = [] (CFrame* frame) -> SharedPointer<CBitmap> {
|
|
auto size = frame->getViewSize ().getSize ();
|
|
if (auto offscreen = COffscreenContext::create (size, 1.))
|
|
{
|
|
offscreen->beginDraw ();
|
|
frame->draw (offscreen);
|
|
offscreen->endDraw ();
|
|
return shared (offscreen->getBitmap ());
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
showEditButton (false);
|
|
|
|
auto origZoom = getFrame ()->getZoom ();
|
|
getFrame ()->setZoom (1.);
|
|
auto bitmap1 = makeScreenshot (getFrame ());
|
|
getFrame ()->setZoom (2.);
|
|
auto bitmap2 = makeScreenshot (getFrame ());
|
|
getFrame ()->setZoom (origZoom);
|
|
|
|
auto folderPath = std::string (fs->getSelectedFile (0));
|
|
auto uidStr = std::string ("XXXXXXXX");
|
|
if (bitmap1)
|
|
{
|
|
auto data = getPlatformFactory ().createBitmapMemoryPNGRepresentation (
|
|
bitmap1->getPlatformBitmap ());
|
|
if (!data.empty ())
|
|
{
|
|
auto filename = folderPath + "/" + uidStr + "_snapshot.png";
|
|
CFileStream stream;
|
|
if (stream.open (filename.data (), CFileStream::kWriteMode |
|
|
CFileStream::kTruncateMode |
|
|
CFileStream::kBinaryMode))
|
|
{
|
|
stream.writeRaw (data.data (), static_cast<uint32_t> (data.size ()));
|
|
}
|
|
}
|
|
}
|
|
if (bitmap2)
|
|
{
|
|
auto filename = folderPath + "/" + uidStr + "_snapshot_2.0x.png";
|
|
auto data = getPlatformFactory ().createBitmapMemoryPNGRepresentation (
|
|
bitmap2->getPlatformBitmap ());
|
|
if (!data.empty ())
|
|
{
|
|
CFileStream stream;
|
|
if (stream.open (filename.data (), CFileStream::kWriteMode |
|
|
CFileStream::kTruncateMode |
|
|
CFileStream::kBinaryMode))
|
|
{
|
|
stream.writeRaw (data.data (), static_cast<uint32_t> (data.size ()));
|
|
}
|
|
}
|
|
}
|
|
if (enableShowEditButton ())
|
|
showEditButton (true);
|
|
});
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool VST3Editor::enableShowEditButton () const
|
|
{
|
|
bool addShowEditorButton = true;
|
|
if (auto attributes = description->getCustomAttributes ("VST3Editor", true))
|
|
{
|
|
attributes->getBooleanAttribute ("Show Editor Button", addShowEditorButton);
|
|
}
|
|
return addShowEditorButton;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::enableShowEditButton (bool state)
|
|
{
|
|
if (auto attributes = description->getCustomAttributes ("VST3Editor", true))
|
|
attributes->setBooleanAttribute ("Show Editor Button", state);
|
|
}
|
|
|
|
#if VSTGUI_LIVE_EDITING
|
|
//------------------------------------------------------------------------
|
|
class EnterEditModeController
|
|
: public ViewListenerAdapter
|
|
, public ViewEventListenerAdapter
|
|
, public IControlListener
|
|
{
|
|
public:
|
|
using EnterEditModeFunc = std::function<void ()>;
|
|
|
|
static constexpr const auto strFull = "Open UI Editor";
|
|
static constexpr const auto strMinimized = "e";
|
|
|
|
EnterEditModeController (CFrame* frame, EnterEditModeFunc&& func)
|
|
: enterEditMode (std::move (func))
|
|
{
|
|
button = new CTextButton ({0, 0, 120, 20});
|
|
button->setTitle (strFull);
|
|
button->setRoundRadius (2.);
|
|
button->setFrameWidth (-1);
|
|
button->registerViewListener (this);
|
|
button->registerViewEventListener (this);
|
|
button->registerControlListener (this);
|
|
frame->addView (button);
|
|
}
|
|
~EnterEditModeController () noexcept override
|
|
{
|
|
if (button)
|
|
{
|
|
unregisterButtonListeners ();
|
|
if (auto parent = button->getParentView ())
|
|
parent->asViewContainer ()->removeView (button);
|
|
}
|
|
}
|
|
|
|
void unregisterButtonListeners ()
|
|
{
|
|
button->unregisterViewEventListener (this);
|
|
button->unregisterViewListener (this);
|
|
button->unregisterControlListener (this);
|
|
}
|
|
|
|
void valueChanged (CControl* c) override
|
|
{
|
|
if (c->getValue () == 1.)
|
|
{
|
|
enterEditMode ();
|
|
}
|
|
}
|
|
void viewAttached (CView* view) override
|
|
{
|
|
view->addAnimation ("SizeAnim", new Animation::AlphaValueAnimation (1.f),
|
|
new Animation::LinearTimingFunction (1000),
|
|
[&] (auto, auto, auto) { close (); });
|
|
}
|
|
|
|
void viewWillDelete (CView* view) override
|
|
{
|
|
if (button == nullptr)
|
|
return;
|
|
unregisterButtonListeners ();
|
|
button = nullptr;
|
|
delete this;
|
|
}
|
|
void viewOnEvent (CView* view, Event& event) override
|
|
{
|
|
if (event.type == EventType::MouseEnter)
|
|
{
|
|
open ();
|
|
}
|
|
else if (event.type == EventType::MouseExit)
|
|
{
|
|
close ();
|
|
}
|
|
}
|
|
|
|
Animation::ITimingFunction* createDefAnimTimingFunc () const
|
|
{
|
|
using namespace Animation;
|
|
|
|
static const constexpr auto AnimationTime = 150;
|
|
return new CubicBezierTimingFunction (CubicBezierTimingFunction::easyInOut (AnimationTime));
|
|
}
|
|
|
|
void open ()
|
|
{
|
|
button->addAnimation ("SizeAnim", new Animation::ViewSizeAnimation ({0, 0, 120, 20}),
|
|
createDefAnimTimingFunc (),
|
|
[&] (auto view, auto, auto) { button->setTitle (strFull); });
|
|
button->addAnimation ("AlphaValue", new Animation::AlphaValueAnimation (1.f),
|
|
createDefAnimTimingFunc ());
|
|
}
|
|
void close ()
|
|
{
|
|
button->addAnimation ("SizeAnim", new Animation::ViewSizeAnimation ({0, 0, 10, 20}),
|
|
createDefAnimTimingFunc (),
|
|
[&] (auto view, auto, auto) { button->setTitle (strMinimized); });
|
|
button->addAnimation ("AlphaValue", new Animation::AlphaValueAnimation (0.3f),
|
|
createDefAnimTimingFunc ());
|
|
}
|
|
|
|
EnterEditModeFunc enterEditMode;
|
|
CTextButton* button {nullptr};
|
|
};
|
|
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::showEditButton (bool state)
|
|
{
|
|
#if VSTGUI_LIVE_EDITING
|
|
if ((state && openUIEditorController) || (!state && openUIEditorController == nullptr))
|
|
return;
|
|
if (state)
|
|
{
|
|
openUIEditorController =
|
|
new EnterEditModeController (getFrame (), [this] () { editingEnabled = true; requestRecreateView (); });
|
|
}
|
|
else
|
|
{
|
|
delete openUIEditorController;
|
|
openUIEditorController = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool VST3Editor::enableEditing (bool state)
|
|
{
|
|
if (getFrame ())
|
|
{
|
|
getFrame ()->removeAll ();
|
|
|
|
#if VSTGUI_LIVE_EDITING
|
|
openUIEditorController = nullptr;
|
|
if (state)
|
|
{
|
|
editingEnabled = true;
|
|
// update uiDesc file path to absolute if possible
|
|
if (UIAttributes* attributes = description->getCustomAttributes ("VST3Editor", true))
|
|
{
|
|
const std::string* filePath = attributes->getAttributeValue ("Path");
|
|
if (filePath)
|
|
{
|
|
CFileStream s;
|
|
if (!s.open (filePath->c_str (), CFileStream::kReadMode))
|
|
{
|
|
attributes->removeAttribute ("Path");
|
|
}
|
|
else
|
|
description->setFilePath (filePath->c_str ());
|
|
}
|
|
}
|
|
|
|
getFrame ()->setTransform (CGraphicsTransform ());
|
|
nonEditRect = getFrame ()->getViewSize ();
|
|
description->setController (this);
|
|
auto* editController = new UIEditController (description);
|
|
CView* view = editController->createEditView ();
|
|
if (view)
|
|
{
|
|
CCoord width = view->getWidth ();
|
|
CCoord height = view->getHeight ();
|
|
|
|
getFrame ()->setSize (width, height);
|
|
getFrame ()->addView (view);
|
|
getFrame ()->setZoom (getContentScaleFactor ());
|
|
|
|
getFrame ()->enableTooltips (true);
|
|
CColor focusColor = kBlueCColor;
|
|
editController->getEditorDescription ()->getColor ("focus", focusColor);
|
|
getFrame ()->setFocusColor (focusColor);
|
|
getFrame ()->setFocusDrawingEnabled (true);
|
|
getFrame ()->setFocusWidth (1);
|
|
|
|
COptionMenu* fileMenu = editController->getMenuController ()->getFileMenu ();
|
|
if (fileMenu)
|
|
{
|
|
CMenuItem* item = fileMenu->addEntry (
|
|
new CCommandMenuItem ({"Save", this, "File", "Save"}), 0);
|
|
item->setKey ("s", kControl);
|
|
item = fileMenu->addEntry (
|
|
new CCommandMenuItem ({"Save As..", this, "File", "Save As"}), 1);
|
|
item->setKey ("s", kShift | kControl);
|
|
item = fileMenu->addEntry (new CCommandMenuItem (
|
|
{"Close Editor", this, "File", "Close UIDescription Editor"}));
|
|
item->setKey ("e", kControl);
|
|
}
|
|
COptionMenu* editMenu = editController->getMenuController ()->getEditMenu ();
|
|
if (editMenu)
|
|
{
|
|
editMenu->addSeparator ();
|
|
editMenu->addEntry (new CCommandMenuItem (
|
|
{"Sync Parameter Tags", this, "Edit", "Sync Parameter Tags"}));
|
|
}
|
|
return true;
|
|
}
|
|
editController->forget ();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
editingEnabled = false;
|
|
CView* view = description->createView (viewName.c_str (), this);
|
|
if (view)
|
|
{
|
|
double scaleFactor = getAbsScaleFactor ();
|
|
CCoord width = view->getWidth () * scaleFactor;
|
|
CCoord height = view->getHeight () * scaleFactor;
|
|
|
|
if (canResize () == Steinberg::kResultTrue && nonEditRect.isEmpty ())
|
|
{
|
|
Steinberg::ViewRect tmp;
|
|
if (getRect ().getWidth () != width)
|
|
tmp.right = getRect ().getWidth ();
|
|
if (getRect ().getHeight () != height)
|
|
tmp.bottom = getRect ().getHeight ();
|
|
if (tmp.getWidth () || tmp.getHeight ())
|
|
{
|
|
if (tmp.getWidth () == 0)
|
|
tmp.right = width;
|
|
if (tmp.getHeight () == 0)
|
|
tmp.bottom = width;
|
|
checkSizeConstraint (&tmp);
|
|
nonEditRect.setWidth (tmp.getWidth ());
|
|
nonEditRect.setHeight (tmp.getHeight ());
|
|
}
|
|
}
|
|
|
|
getFrame ()->setSize (width, height);
|
|
getFrame ()->addView (view);
|
|
getFrame ()->setTransform (CGraphicsTransform ().scale (scaleFactor, scaleFactor));
|
|
getFrame ()->invalid ();
|
|
if (nonEditRect.isEmpty () == false)
|
|
{
|
|
Steinberg::ViewRect tmpRect = rect;
|
|
tmpRect.right = tmpRect.left + (Steinberg::int32)nonEditRect.getWidth ();
|
|
tmpRect.bottom = tmpRect.top + (Steinberg::int32)nonEditRect.getHeight ();
|
|
plugFrame->resizeView (this, &tmpRect);
|
|
}
|
|
else
|
|
{
|
|
rect.right = rect.left + width;
|
|
rect.bottom = rect.top + height;
|
|
requestResize ({width, height});
|
|
}
|
|
|
|
getFrame ()->setFocusDrawingEnabled (false);
|
|
|
|
// focus drawing support
|
|
UIAttributes* attributes = description->getCustomAttributes ("FocusDrawing", true);
|
|
|
|
// map old one
|
|
UIAttributes* oldAttributes = description->getCustomAttributes ("VST3Editor");
|
|
if (oldAttributes)
|
|
{
|
|
const std::string* attr = oldAttributes->getAttributeValue (kFrameEnableFocusDrawingAttr);
|
|
if (attr)
|
|
{
|
|
if (*attr == "true")
|
|
{
|
|
attributes->setAttribute ("enabled", "true");
|
|
attr = oldAttributes->getAttributeValue (kFrameFocusColorAttr);
|
|
if (attr)
|
|
{
|
|
attributes->setAttribute ("color", *attr);
|
|
}
|
|
attr = oldAttributes->getAttributeValue (kFrameFocusWidthAttr);
|
|
if (attr)
|
|
{
|
|
attributes->setAttribute ("width", *attr);
|
|
}
|
|
}
|
|
oldAttributes->removeAttribute (kFrameFocusColorAttr);
|
|
oldAttributes->removeAttribute (kFrameFocusWidthAttr);
|
|
oldAttributes->removeAttribute (kFrameEnableFocusDrawingAttr);
|
|
}
|
|
}
|
|
// new one
|
|
const std::string* attr = attributes->getAttributeValue ("enabled");
|
|
if (attr && *attr == "true")
|
|
{
|
|
getFrame ()->setFocusDrawingEnabled (true);
|
|
attr = attributes->getAttributeValue ("color");
|
|
if (attr)
|
|
{
|
|
CColor focusColor;
|
|
if (description->getColor (attr->c_str (), focusColor))
|
|
getFrame ()->setFocusColor (focusColor);
|
|
}
|
|
attr = attributes->getAttributeValue ("width");
|
|
if (attr)
|
|
{
|
|
double focusWidth = UTF8StringView (attr->c_str ()).toDouble ();
|
|
getFrame ()->setFocusWidth (focusWidth);
|
|
}
|
|
}
|
|
#if VSTGUI_LIVE_EDITING
|
|
if (enableShowEditButton ())
|
|
showEditButton (true);
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void VST3Editor::setDelegate (IVST3EditorDelegate* inDelegate)
|
|
{
|
|
delegate = inDelegate;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
IVST3EditorDelegate* VST3Editor::getDelegate () const
|
|
{
|
|
return delegate;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
UIDescription* VST3Editor::getUIDescription () const
|
|
{
|
|
return description;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//--- AspectRatioVST3Editor
|
|
//------------------------------------------------------------------------
|
|
void AspectRatioVST3Editor::setMinZoomFactor (double factor) { minZoomFactor = factor; }
|
|
|
|
//------------------------------------------------------------------------
|
|
double AspectRatioVST3Editor::getMinZoomFactor () const { return minZoomFactor; }
|
|
|
|
//------------------------------------------------------------------------
|
|
bool PLUGIN_API AspectRatioVST3Editor::open (void* parent, const PlatformType& type)
|
|
{
|
|
calcZoomFactor = getZoomFactor ();
|
|
if (VST3Editor::open (parent, type) && getFrame ())
|
|
{
|
|
initialSize = getFrame ()->getViewSize ().getSize ();
|
|
initialSize /= calcZoomFactor;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API AspectRatioVST3Editor::onSize (Steinberg::ViewRect* newSize)
|
|
{
|
|
if (newSize == nullptr)
|
|
return Steinberg::kInvalidArgument;
|
|
|
|
if (!canCalculateAspectRatio ())
|
|
return VST3Editor::onSize (newSize);
|
|
|
|
setZoomFactor (calcZoomFactor);
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API AspectRatioVST3Editor::checkSizeConstraint (Steinberg::ViewRect* rect)
|
|
{
|
|
if (rect == nullptr)
|
|
return Steinberg::kInvalidArgument;
|
|
|
|
if (!canCalculateAspectRatio ())
|
|
return VST3Editor::checkSizeConstraint (rect);
|
|
|
|
const CPoint size (rect->getWidth (), rect->getHeight ());
|
|
auto diff = size - initialSize;
|
|
auto anchor = initialSize.x >= initialSize.y ? initialSize.x : initialSize.y;
|
|
auto sizeAnchor = initialSize.x >= initialSize.y ? size.x : size.y;
|
|
|
|
auto factor = sizeAnchor / anchor;
|
|
if (factor < minZoomFactor * getContentScaleFactor ())
|
|
factor = minZoomFactor * getContentScaleFactor ();
|
|
factor = std::round (factor * anchor) / anchor;
|
|
auto newSize = initialSize * factor;
|
|
calcZoomFactor = factor / getContentScaleFactor ();
|
|
if (newSize != size)
|
|
{
|
|
rect->right = rect->left + newSize.x;
|
|
rect->bottom = rect->top + newSize.y;
|
|
}
|
|
return Steinberg::kResultTrue;
|
|
}
|
|
|
|
#ifdef VST3_CONTENT_SCALE_SUPPORT
|
|
//------------------------------------------------------------------------
|
|
Steinberg::tresult PLUGIN_API AspectRatioVST3Editor::setContentScaleFactor (ScaleFactor factor)
|
|
{
|
|
auto res = VST3Editor::setContentScaleFactor (factor);
|
|
if (res == Steinberg::kResultTrue && canCalculateAspectRatio ())
|
|
setZoomFactor (calcZoomFactor);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------
|
|
bool AspectRatioVST3Editor::canCalculateAspectRatio () const
|
|
{
|
|
auto f = getFrame ();
|
|
if (inEditMode () || f == nullptr || f->hasChildren () == false)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
} // VSTGUI
|