
* re-apply scroll multiplier for all OSes * multiply const directly --------- Co-authored-by: dromer <alexander@puikheid.nl>
1293 lines
44 KiB
C++
1293 lines
44 KiB
C++
/*
|
|
* DISTRHO Cardinal Plugin
|
|
* Copyright (C) 2021-2024 Filipe Coelho <falktx@falktx.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 3 of
|
|
* the License, or any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* For a full copy of the GNU General Public License see the LICENSE file.
|
|
*/
|
|
|
|
#include <app/MenuBar.hpp>
|
|
#include <app/Scene.hpp>
|
|
#include <asset.hpp>
|
|
#include <context.hpp>
|
|
#include <engine/Engine.hpp>
|
|
#include <helpers.hpp>
|
|
#include <patch.hpp>
|
|
#include <settings.hpp>
|
|
#include <string.hpp>
|
|
#include <system.hpp>
|
|
#include <ui/Button.hpp>
|
|
#include <ui/MenuItem.hpp>
|
|
#include <ui/MenuSeparator.hpp>
|
|
#include <window/Window.hpp>
|
|
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
# include <plugin.hpp>
|
|
#endif
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
# include <ui/Button.hpp>
|
|
# include <ui/Label.hpp>
|
|
# include <ui/MenuOverlay.hpp>
|
|
# include <ui/SequentialLayout.hpp>
|
|
# include <emscripten/emscripten.h>
|
|
#endif
|
|
|
|
#ifdef NDEBUG
|
|
# undef DEBUG
|
|
#endif
|
|
|
|
#include "Application.hpp"
|
|
#include "AsyncDialog.hpp"
|
|
#include "CardinalCommon.hpp"
|
|
#include "CardinalPluginContext.hpp"
|
|
#include "WindowParameters.hpp"
|
|
#include "extra/Base64.hpp"
|
|
|
|
#ifndef DISTRHO_OS_WASM
|
|
# include "extra/SharedResourcePointer.hpp"
|
|
#endif
|
|
|
|
#ifndef HEADLESS
|
|
# include "extra/ScopedValueSetter.hpp"
|
|
#endif
|
|
|
|
namespace rack {
|
|
#ifdef DISTRHO_OS_WASM
|
|
namespace asset {
|
|
std::string patchesPath();
|
|
}
|
|
#endif
|
|
namespace engine {
|
|
void Engine_setAboutToClose(Engine*);
|
|
void Engine_setRemoteDetails(Engine*, remoteUtils::RemoteDetails*);
|
|
}
|
|
namespace window {
|
|
void WindowSetPluginUI(Window* window, CardinalBaseUI* ui);
|
|
void WindowSetMods(Window* window, int mods);
|
|
void WindowSetInternalSize(rack::window::Window* window, math::Vec size);
|
|
}
|
|
}
|
|
|
|
START_NAMESPACE_DISTRHO
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
uint32_t Plugin::getBufferSize() const noexcept { return 0; }
|
|
double Plugin::getSampleRate() const noexcept { return 0.0; }
|
|
const char* Plugin::getBundlePath() const noexcept { return nullptr; }
|
|
bool Plugin::isSelfTestInstance() const noexcept { return false; }
|
|
bool Plugin::writeMidiEvent(const MidiEvent&) noexcept { return false; }
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
#if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
|
|
struct WasmWelcomeDialog : rack::widget::OpaqueWidget
|
|
{
|
|
static const constexpr float margin = 10;
|
|
static const constexpr float buttonWidth = 110;
|
|
|
|
WasmWelcomeDialog()
|
|
{
|
|
using rack::ui::Button;
|
|
using rack::ui::Label;
|
|
using rack::ui::MenuOverlay;
|
|
using rack::ui::SequentialLayout;
|
|
|
|
box.size = rack::math::Vec(550, 310);
|
|
|
|
SequentialLayout* const layout = new SequentialLayout;
|
|
layout->box.pos = rack::math::Vec(0, 0);
|
|
layout->box.size = box.size;
|
|
layout->orientation = SequentialLayout::VERTICAL_ORIENTATION;
|
|
layout->margin = rack::math::Vec(margin, margin);
|
|
layout->spacing = rack::math::Vec(margin, margin);
|
|
layout->wrap = false;
|
|
addChild(layout);
|
|
|
|
SequentialLayout* const contentLayout = new SequentialLayout;
|
|
contentLayout->spacing = rack::math::Vec(margin, margin);
|
|
layout->addChild(contentLayout);
|
|
|
|
SequentialLayout* const buttonLayout = new SequentialLayout;
|
|
buttonLayout->alignment = SequentialLayout::CENTER_ALIGNMENT;
|
|
buttonLayout->box.size = box.size;
|
|
buttonLayout->spacing = rack::math::Vec(margin, margin);
|
|
layout->addChild(buttonLayout);
|
|
|
|
Label* const label = new Label;
|
|
label->box.size.x = box.size.x - 2*margin;
|
|
label->box.size.y = box.size.y - 2*margin - 40;
|
|
label->fontSize = 20;
|
|
label->text = ""
|
|
"Welcome to Cardinal on the Web!\n"
|
|
"\n"
|
|
"If using mobile/touch devices, please note:\n"
|
|
" - Single quick press does simple mouse click\n"
|
|
" - Press & move does click & drag action\n"
|
|
" - Press & hold does right-click (and opens module browser)\n"
|
|
"\n"
|
|
"Still a bit experimental, so proceed with caution.\n"
|
|
"Have fun!";
|
|
contentLayout->addChild(label);
|
|
|
|
struct JoinDiscussionButton : Button {
|
|
WasmWelcomeDialog* dialog;
|
|
void onAction(const ActionEvent& e) override {
|
|
patchUtils::openBrowser("https://github.com/DISTRHO/Cardinal/issues/287");
|
|
dialog->getParent()->requestDelete();
|
|
}
|
|
};
|
|
JoinDiscussionButton* const discussionButton = new JoinDiscussionButton;
|
|
discussionButton->box.size.x = buttonWidth;
|
|
discussionButton->text = "Join discussion";
|
|
discussionButton->dialog = this;
|
|
buttonLayout->addChild(discussionButton);
|
|
|
|
struct DismissButton : Button {
|
|
WasmWelcomeDialog* dialog;
|
|
void onAction(const ActionEvent& e) override {
|
|
dialog->getParent()->requestDelete();
|
|
}
|
|
};
|
|
DismissButton* const dismissButton = new DismissButton;
|
|
dismissButton->box.size.x = buttonWidth;
|
|
dismissButton->text = "Dismiss";
|
|
dismissButton->dialog = this;
|
|
buttonLayout->addChild(dismissButton);
|
|
|
|
MenuOverlay* const overlay = new MenuOverlay;
|
|
overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
|
|
overlay->addChild(this);
|
|
APP->scene->addChild(overlay);
|
|
}
|
|
|
|
void step() override
|
|
{
|
|
OpaqueWidget::step();
|
|
box.pos = parent->box.size.minus(box.size).div(2).round();
|
|
}
|
|
|
|
void draw(const DrawArgs& args) override
|
|
{
|
|
bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
|
|
Widget::draw(args);
|
|
}
|
|
};
|
|
|
|
struct WasmRemotePatchLoadingDialog : rack::widget::OpaqueWidget
|
|
{
|
|
static const constexpr float margin = 10;
|
|
|
|
rack::ui::MenuOverlay* overlay;
|
|
|
|
WasmRemotePatchLoadingDialog(const bool isFromPatchStorage)
|
|
{
|
|
using rack::ui::Label;
|
|
using rack::ui::MenuOverlay;
|
|
using rack::ui::SequentialLayout;
|
|
|
|
box.size = rack::math::Vec(300, 40);
|
|
|
|
SequentialLayout* const layout = new SequentialLayout;
|
|
layout->box.pos = rack::math::Vec(0, 0);
|
|
layout->box.size = box.size;
|
|
layout->alignment = SequentialLayout::CENTER_ALIGNMENT;
|
|
layout->margin = rack::math::Vec(margin, margin);
|
|
layout->spacing = rack::math::Vec(margin, margin);
|
|
layout->wrap = false;
|
|
addChild(layout);
|
|
|
|
Label* const label = new Label;
|
|
label->box.size.x = box.size.x - 2*margin;
|
|
label->box.size.y = box.size.y - 2*margin;
|
|
label->fontSize = 16;
|
|
label->text = isFromPatchStorage
|
|
? "Loading patch from PatchStorage...\n"
|
|
: "Loading remote patch...\n";
|
|
layout->addChild(label);
|
|
|
|
overlay = new MenuOverlay;
|
|
overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
|
|
overlay->addChild(this);
|
|
APP->scene->addChild(overlay);
|
|
}
|
|
|
|
void step() override
|
|
{
|
|
OpaqueWidget::step();
|
|
box.pos = parent->box.size.minus(box.size).div(2).round();
|
|
}
|
|
|
|
void draw(const DrawArgs& args) override
|
|
{
|
|
bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
|
|
Widget::draw(args);
|
|
}
|
|
};
|
|
|
|
static void downloadRemotePatchFailed(const char* const filename)
|
|
{
|
|
d_stdout("downloadRemotePatchFailed %s", filename);
|
|
CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
|
|
CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
|
|
|
|
if (ui->psDialog != nullptr)
|
|
{
|
|
ui->psDialog->overlay->requestDelete();
|
|
ui->psDialog = nullptr;
|
|
asyncDialog::create("Failed to fetch remote patch");
|
|
}
|
|
|
|
using namespace rack;
|
|
context->patch->templatePath = rack::system::join(asset::patchesPath(), "templates/main.vcv");
|
|
context->patch->loadTemplate();
|
|
context->scene->rackScroll->reset();
|
|
}
|
|
|
|
static void downloadRemotePatchSucceeded(const char* const filename)
|
|
{
|
|
d_stdout("downloadRemotePatchSucceeded %s | %s", filename, APP->patch->templatePath.c_str());
|
|
CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
|
|
CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
|
|
|
|
ui->psDialog->overlay->requestDelete();
|
|
ui->psDialog = nullptr;
|
|
|
|
if (FILE* f = fopen(filename, "r"))
|
|
{
|
|
uint8_t buf[8] = {};
|
|
fread(buf, 8, 1, f);
|
|
d_stdout("read patch %x %x %x %x %x %x %x %x",
|
|
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
|
|
fclose(f);
|
|
}
|
|
|
|
try {
|
|
context->patch->load(filename);
|
|
} catch (rack::Exception& e) {
|
|
const std::string message = rack::string::f("Could not load patch: %s", e.what());
|
|
asyncDialog::create(message.c_str());
|
|
return;
|
|
}
|
|
|
|
context->patch->path.clear();
|
|
context->scene->rackScroll->reset();
|
|
context->history->setSaved();
|
|
}
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
class CardinalUI : public CardinalBaseUI,
|
|
public WindowParametersCallback
|
|
{
|
|
#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
#ifdef DISTRHO_OS_WASM
|
|
ScopedPointer<Initializer> fInitializer;
|
|
#else
|
|
SharedResourcePointer<Initializer> fInitializer;
|
|
#endif
|
|
std::string fAutosavePath;
|
|
#endif
|
|
|
|
rack::math::Vec lastMousePos;
|
|
WindowParameters windowParameters;
|
|
int rateLimitStep = 0;
|
|
#if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
|
|
int8_t counterForFirstIdlePoint = 0;
|
|
#endif
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
bool inSelfTest = false;
|
|
#endif
|
|
|
|
struct ScopedContext {
|
|
CardinalPluginContext* const context;
|
|
|
|
ScopedContext(CardinalUI* const ui)
|
|
: context(ui->context)
|
|
{
|
|
rack::contextSet(context);
|
|
WindowParametersRestore(context->window);
|
|
}
|
|
|
|
ScopedContext(CardinalUI* const ui, const int mods)
|
|
: context(ui->context)
|
|
{
|
|
rack::contextSet(context);
|
|
rack::window::WindowSetMods(context->window, mods);
|
|
WindowParametersRestore(context->window);
|
|
}
|
|
|
|
~ScopedContext()
|
|
{
|
|
if (context->window != nullptr)
|
|
WindowParametersSave(context->window);
|
|
}
|
|
};
|
|
|
|
public:
|
|
CardinalUI()
|
|
: CardinalBaseUI(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT),
|
|
#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
#ifdef DISTRHO_OS_WASM
|
|
fInitializer(new Initializer(static_cast<const CardinalBasePlugin*>(nullptr), this)),
|
|
#else
|
|
fInitializer(static_cast<const CardinalBasePlugin*>(nullptr), this),
|
|
#endif
|
|
#endif
|
|
lastMousePos()
|
|
{
|
|
rack::contextSet(context);
|
|
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
// create unique temporary path for this instance
|
|
try {
|
|
char uidBuf[24];
|
|
const std::string tmp = rack::system::getTempDirectory();
|
|
|
|
for (int i=1;; ++i)
|
|
{
|
|
std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
|
|
const std::string trypath = rack::system::join(tmp, uidBuf);
|
|
|
|
if (! rack::system::exists(trypath))
|
|
{
|
|
if (rack::system::createDirectories(trypath))
|
|
fAutosavePath = trypath;
|
|
break;
|
|
}
|
|
}
|
|
} DISTRHO_SAFE_EXCEPTION("create unique temporary path");
|
|
|
|
const float sampleRate = 60; // fake audio running at 60 fps
|
|
rack::settings::sampleRate = sampleRate;
|
|
|
|
context->dataIns = new const float*[DISTRHO_PLUGIN_NUM_INPUTS];
|
|
context->dataOuts = new float*[DISTRHO_PLUGIN_NUM_OUTPUTS];
|
|
|
|
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS;++i)
|
|
{
|
|
float** const bufferptr = const_cast<float**>(&context->dataIns[i]);
|
|
*bufferptr = new float[1];
|
|
(*bufferptr)[0] = 0.f;
|
|
}
|
|
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS;++i)
|
|
context->dataOuts[i] = new float[1];
|
|
|
|
context->bufferSize = 1;
|
|
context->sampleRate = sampleRate;
|
|
|
|
context->engine = new rack::engine::Engine;
|
|
context->engine->setSampleRate(sampleRate);
|
|
|
|
context->history = new rack::history::State;
|
|
context->patch = new rack::patch::Manager;
|
|
context->patch->autosavePath = fAutosavePath;
|
|
context->patch->templatePath = context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath;
|
|
|
|
context->event = new rack::widget::EventState;
|
|
context->scene = new rack::app::Scene;
|
|
context->event->rootWidget = context->scene;
|
|
|
|
context->window = new rack::window::Window;
|
|
|
|
context->patch->loadTemplate();
|
|
context->scene->rackScroll->reset();
|
|
|
|
DISTRHO_SAFE_ASSERT(remoteUtils::connectToRemote(CARDINAL_DEFAULT_REMOTE_URL));
|
|
|
|
Engine_setRemoteDetails(context->engine, remoteDetails);
|
|
#endif
|
|
|
|
Window& window(getWindow());
|
|
|
|
window.setIgnoringKeyRepeat(true);
|
|
context->nativeWindowId = window.getNativeWindowHandle();
|
|
|
|
const double scaleFactor = getScaleFactor();
|
|
|
|
setGeometryConstraints(648 * scaleFactor, 538 * scaleFactor);
|
|
|
|
if (rack::isStandalone() && rack::system::exists(rack::settings::settingsPath))
|
|
{
|
|
const double width = std::max(648.f, rack::settings::windowSize.x) * scaleFactor;
|
|
const double height = std::max(538.f, rack::settings::windowSize.y) * scaleFactor;
|
|
setSize(width, height);
|
|
}
|
|
|
|
#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
const DGL_NAMESPACE::Window::ScopedGraphicsContext sgc(window);
|
|
#endif
|
|
|
|
rack::window::WindowSetPluginUI(context->window, this);
|
|
|
|
// hide "Browse VCV Library" button
|
|
rack::widget::Widget* const browser = context->scene->browser->children.back();
|
|
rack::widget::Widget* const headerLayout = browser->children.front();
|
|
rack::widget::Widget* const libraryButton = headerLayout->children.back();
|
|
libraryButton->hide();
|
|
|
|
// Report to user if something is wrong with the installation
|
|
std::string errorMessage;
|
|
|
|
if (rack::asset::systemDir.empty())
|
|
{
|
|
errorMessage = "Failed to locate Cardinal plugin bundle.\n"
|
|
"Install Cardinal with its plugin bundle folder intact and try again.";
|
|
}
|
|
else if (! rack::system::exists(rack::asset::systemDir))
|
|
{
|
|
errorMessage = rack::string::f("System directory \"%s\" does not exist. "
|
|
"Make sure Cardinal was downloaded and installed correctly.",
|
|
rack::asset::systemDir.c_str());
|
|
}
|
|
|
|
if (! errorMessage.empty())
|
|
{
|
|
static bool shown = false;
|
|
|
|
if (! shown)
|
|
{
|
|
shown = true;
|
|
asyncDialog::create(errorMessage.c_str());
|
|
}
|
|
}
|
|
|
|
#if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
|
|
if (rack::patchStorageSlug != nullptr)
|
|
{
|
|
psDialog = new WasmRemotePatchLoadingDialog(true);
|
|
}
|
|
else if (rack::patchRemoteURL != nullptr)
|
|
{
|
|
psDialog = new WasmRemotePatchLoadingDialog(false);
|
|
}
|
|
else if (rack::patchFromURL != nullptr)
|
|
{
|
|
static_cast<CardinalBasePlugin*>(context->plugin)->setState("patch", rack::patchFromURL);
|
|
rack::contextSet(context);
|
|
}
|
|
else
|
|
{
|
|
new WasmWelcomeDialog();
|
|
}
|
|
#endif
|
|
|
|
context->window->step();
|
|
|
|
rack::contextSet(nullptr);
|
|
|
|
WindowParametersSetCallback(context->window, this);
|
|
}
|
|
|
|
~CardinalUI() override
|
|
{
|
|
rack::contextSet(context);
|
|
|
|
context->nativeWindowId = 0;
|
|
|
|
rack::window::WindowSetPluginUI(context->window, nullptr);
|
|
|
|
context->tlw = nullptr;
|
|
context->ui = nullptr;
|
|
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
{
|
|
const ScopedContext sc(this);
|
|
context->patch->clear();
|
|
|
|
// do a little dance to prevent context scene deletion from saving to temp dir
|
|
const ScopedValueSetter<bool> svs(rack::settings::headless, true);
|
|
Engine_setAboutToClose(context->engine);
|
|
delete context;
|
|
}
|
|
|
|
if (! fAutosavePath.empty())
|
|
rack::system::removeRecursively(fAutosavePath);
|
|
#endif
|
|
|
|
#if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
if (fInitializer->shouldSaveSettings)
|
|
{
|
|
INFO("Save settings");
|
|
rack::settings::save();
|
|
}
|
|
#endif
|
|
|
|
rack::contextSet(nullptr);
|
|
}
|
|
|
|
void onNanoDisplay() override
|
|
{
|
|
const ScopedContext sc(this);
|
|
context->window->step();
|
|
}
|
|
|
|
void uiIdle() override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest)
|
|
{
|
|
context->window->step();
|
|
return;
|
|
}
|
|
|
|
if (context->plugin->isSelfTestInstance())
|
|
{
|
|
inSelfTest = true;
|
|
|
|
Application& app(getApp());
|
|
|
|
const ScopedContext sc(this);
|
|
|
|
context->patch->clear();
|
|
app.idle();
|
|
|
|
const rack::math::Vec mousePos(getWidth()/2,getHeight()/2);
|
|
context->event->handleButton(mousePos, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, 0x0);
|
|
context->event->handleHover(mousePos, rack::math::Vec(0,0));
|
|
app.idle();
|
|
|
|
for (rack::plugin::Plugin* p : rack::plugin::plugins)
|
|
{
|
|
for (rack::plugin::Model* m : p->models)
|
|
{
|
|
d_stdout(">>>>>>>>>>>>>>>>> LOADING module %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
rack::engine::Module* const module = m->createModule();
|
|
DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr);
|
|
|
|
rack::CardinalPluginModelHelper* const helper = dynamic_cast<rack::CardinalPluginModelHelper*>(m);
|
|
DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr);
|
|
|
|
d_stdout(">>>>>>>>>>>>>>>>> LOADING moduleWidget %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
rack::app::ModuleWidget* const moduleWidget = helper->createModuleWidget(module);
|
|
DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr);
|
|
|
|
d_stdout(">>>>>>>>>>>>>>>>> ADDING TO ENGINE %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
context->engine->addModule(module);
|
|
|
|
d_stdout(">>>>>>>>>>>>>>>>> ADDING TO RACK VIEW %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
context->scene->rack->addModuleAtMouse(moduleWidget);
|
|
|
|
for (int i=5; --i>=0;)
|
|
app.idle();
|
|
|
|
d_stdout(">>>>>>>>>>>>>>>>> REMOVING FROM RACK VIEW %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
context->scene->rack->removeModule(moduleWidget);
|
|
app.idle();
|
|
|
|
d_stdout(">>>>>>>>>>>>>>>>> DELETING module + moduleWidget %s : %s", p->slug.c_str(), m->slug.c_str());
|
|
delete moduleWidget;
|
|
|
|
app.idle();
|
|
}
|
|
}
|
|
|
|
inSelfTest = false;
|
|
}
|
|
#endif
|
|
|
|
#if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
|
|
if (counterForFirstIdlePoint >= 0 && ++counterForFirstIdlePoint == 30)
|
|
{
|
|
counterForFirstIdlePoint = -1;
|
|
|
|
if (rack::patchStorageSlug != nullptr)
|
|
{
|
|
std::string url("/patchstorage.php?slug=");
|
|
url += rack::patchStorageSlug;
|
|
std::free(rack::patchStorageSlug);
|
|
rack::patchStorageSlug = nullptr;
|
|
|
|
emscripten_async_wget(url.c_str(), context->patch->templatePath.c_str(),
|
|
downloadRemotePatchSucceeded, downloadRemotePatchFailed);
|
|
}
|
|
else if (rack::patchRemoteURL != nullptr)
|
|
{
|
|
std::string url("/patchurl.php?url=");
|
|
url += rack::patchRemoteURL;
|
|
std::free(rack::patchRemoteURL);
|
|
rack::patchRemoteURL = nullptr;
|
|
|
|
emscripten_async_wget(url.c_str(), context->patch->templatePath.c_str(),
|
|
downloadRemotePatchSucceeded, downloadRemotePatchFailed);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle))
|
|
{
|
|
{
|
|
const char* const path = fileBrowserGetPath(filebrowserhandle);
|
|
|
|
const ScopedContext sc(this);
|
|
filebrowseraction(path != nullptr ? strdup(path) : nullptr);
|
|
}
|
|
|
|
fileBrowserClose(filebrowserhandle);
|
|
filebrowseraction = nullptr;
|
|
filebrowserhandle = nullptr;
|
|
}
|
|
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
{
|
|
const ScopedContext sc(this);
|
|
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS;++i)
|
|
context->dataOuts[i][0] = 0.f;
|
|
++context->processCounter;
|
|
context->engine->stepBlock(1);
|
|
}
|
|
#endif
|
|
|
|
if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2))
|
|
return;
|
|
|
|
rateLimitStep = 0;
|
|
repaint();
|
|
}
|
|
|
|
void WindowParametersChanged(const WindowParameterList param, float value) override
|
|
{
|
|
float mult = 1.0f;
|
|
|
|
switch (param)
|
|
{
|
|
case kWindowParameterShowTooltips:
|
|
windowParameters.tooltips = value > 0.5f;
|
|
break;
|
|
case kWindowParameterCableOpacity:
|
|
mult = 100.0f;
|
|
windowParameters.cableOpacity = value;
|
|
break;
|
|
case kWindowParameterCableTension:
|
|
mult = 100.0f;
|
|
windowParameters.cableTension = value;
|
|
break;
|
|
case kWindowParameterRackBrightness:
|
|
mult = 100.0f;
|
|
windowParameters.rackBrightness = value;
|
|
break;
|
|
case kWindowParameterHaloBrightness:
|
|
mult = 100.0f;
|
|
windowParameters.haloBrightness = value;
|
|
break;
|
|
case kWindowParameterKnobMode:
|
|
switch (static_cast<int>(value + 0.5f))
|
|
{
|
|
case rack::settings::KNOB_MODE_LINEAR:
|
|
value = 0;
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
|
|
break;
|
|
case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
|
|
value = 1;
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
|
|
break;
|
|
case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
|
|
value = 2;
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
|
|
break;
|
|
}
|
|
break;
|
|
case kWindowParameterWheelKnobControl:
|
|
windowParameters.knobScroll = value > 0.5f;
|
|
break;
|
|
case kWindowParameterWheelSensitivity:
|
|
mult = 1000.0f;
|
|
windowParameters.knobScrollSensitivity = value;
|
|
break;
|
|
case kWindowParameterLockModulePositions:
|
|
windowParameters.lockModules = value > 0.5f;
|
|
break;
|
|
case kWindowParameterUpdateRateLimit:
|
|
windowParameters.rateLimit = static_cast<int>(value + 0.5f);
|
|
rateLimitStep = 0;
|
|
break;
|
|
case kWindowParameterBrowserSort:
|
|
windowParameters.browserSort = static_cast<int>(value + 0.5f);
|
|
break;
|
|
case kWindowParameterBrowserZoom:
|
|
windowParameters.browserZoom = value;
|
|
value = std::pow(2.f, value) * 100.0f;
|
|
break;
|
|
case kWindowParameterInvertZoom:
|
|
windowParameters.invertZoom = value > 0.5f;
|
|
break;
|
|
case kWindowParameterSqueezeModulePositions:
|
|
windowParameters.squeezeModules = value > 0.5f;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
setParameterValue(kCardinalParameterStartWindow + param, value * mult);
|
|
}
|
|
|
|
protected:
|
|
/* --------------------------------------------------------------------------------------------------------
|
|
* DSP/Plugin Callbacks */
|
|
|
|
/**
|
|
A parameter has changed on the plugin side.
|
|
This is called by the host to inform the UI about parameter changes.
|
|
*/
|
|
void parameterChanged(const uint32_t index, const float value) override
|
|
{
|
|
// host mapped parameters
|
|
if (index < kCardinalParameterCountAtModules)
|
|
{
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
context->parameters[index] = value;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// bypass
|
|
if (index == kCardinalParameterBypass)
|
|
{
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
context->bypassed = value > 0.5f;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (index < kCardinalParameterCountAtWindow)
|
|
{
|
|
switch (index - kCardinalParameterStartWindow)
|
|
{
|
|
case kWindowParameterShowTooltips:
|
|
windowParameters.tooltips = value > 0.5f;
|
|
break;
|
|
case kWindowParameterCableOpacity:
|
|
windowParameters.cableOpacity = value / 100.0f;
|
|
break;
|
|
case kWindowParameterCableTension:
|
|
windowParameters.cableTension = value / 100.0f;
|
|
break;
|
|
case kWindowParameterRackBrightness:
|
|
windowParameters.rackBrightness = value / 100.0f;
|
|
break;
|
|
case kWindowParameterHaloBrightness:
|
|
windowParameters.haloBrightness = value / 100.0f;
|
|
break;
|
|
case kWindowParameterKnobMode:
|
|
switch (static_cast<int>(value + 0.5f))
|
|
{
|
|
case 0:
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
|
|
break;
|
|
case 1:
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
|
|
break;
|
|
case 2:
|
|
windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
|
|
break;
|
|
}
|
|
break;
|
|
case kWindowParameterWheelKnobControl:
|
|
windowParameters.knobScroll = value > 0.5f;
|
|
break;
|
|
case kWindowParameterWheelSensitivity:
|
|
windowParameters.knobScrollSensitivity = value / 1000.0f;
|
|
break;
|
|
case kWindowParameterLockModulePositions:
|
|
windowParameters.lockModules = value > 0.5f;
|
|
break;
|
|
case kWindowParameterUpdateRateLimit:
|
|
windowParameters.rateLimit = static_cast<int>(value + 0.5f);
|
|
rateLimitStep = 0;
|
|
break;
|
|
case kWindowParameterBrowserSort:
|
|
windowParameters.browserSort = static_cast<int>(value + 0.5f);
|
|
break;
|
|
case kWindowParameterBrowserZoom:
|
|
// round up to nearest valid value
|
|
{
|
|
float rvalue = value - 1.0f;
|
|
|
|
if (rvalue <= 25.0f)
|
|
rvalue = -2.0f;
|
|
else if (rvalue <= 35.0f)
|
|
rvalue = -1.5f;
|
|
else if (rvalue <= 50.0f)
|
|
rvalue = -1.0f;
|
|
else if (rvalue <= 71.0f)
|
|
rvalue = -0.5f;
|
|
else if (rvalue <= 100.0f)
|
|
rvalue = 0.0f;
|
|
else if (rvalue <= 141.0f)
|
|
rvalue = 0.5f;
|
|
else if (rvalue <= 200.0f)
|
|
rvalue = 1.0f;
|
|
else
|
|
rvalue = 0.0f;
|
|
|
|
windowParameters.browserZoom = rvalue;
|
|
}
|
|
break;
|
|
case kWindowParameterInvertZoom:
|
|
windowParameters.invertZoom = value > 0.5f;
|
|
break;
|
|
case kWindowParameterSqueezeModulePositions:
|
|
windowParameters.squeezeModules = value > 0.5f;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
WindowParametersSetValues(context->window, windowParameters);
|
|
return;
|
|
}
|
|
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
if (index < kCardinalParameterCountAtMiniBuffers)
|
|
{
|
|
float* const buffer = *const_cast<float**>(&context->dataIns[index - kCardinalParameterStartMiniBuffers]);
|
|
buffer[0] = value;
|
|
return;
|
|
}
|
|
|
|
switch (index)
|
|
{
|
|
case kCardinalParameterMiniTimeFlags: {
|
|
const int32_t flags = static_cast<int32_t>(value + 0.5f);
|
|
context->playing = flags & 0x1;
|
|
context->bbtValid = flags & 0x2;
|
|
context->reset = flags & 0x4;
|
|
return;
|
|
}
|
|
case kCardinalParameterMiniTimeBar:
|
|
context->bar = static_cast<int32_t>(value + 0.5f);
|
|
return;
|
|
case kCardinalParameterMiniTimeBeat:
|
|
context->beat = static_cast<int32_t>(value + 0.5f);
|
|
return;
|
|
case kCardinalParameterMiniTimeBeatsPerBar:
|
|
context->beatsPerBar = static_cast<int32_t>(value + 0.5f);
|
|
return;
|
|
case kCardinalParameterMiniTimeBeatType:
|
|
context->beatType = static_cast<int32_t>(value + 0.5f);
|
|
context->ticksPerClock = context->ticksPerBeat / context->beatType;
|
|
context->tickClock = std::fmod(context->tick, context->ticksPerClock);
|
|
return;
|
|
case kCardinalParameterMiniTimeFrame:
|
|
context->frame = static_cast<uint64_t>(value * context->sampleRate + 0.5f);
|
|
return;
|
|
case kCardinalParameterMiniTimeBarStartTick:
|
|
context->barStartTick = value;
|
|
return;
|
|
case kCardinalParameterMiniTimeBeatsPerMinute:
|
|
context->beatsPerMinute = value;
|
|
context->ticksPerFrame = 1.0 / (60.0 * context->sampleRate / context->beatsPerMinute / context->ticksPerBeat);
|
|
return;
|
|
case kCardinalParameterMiniTimeTick:
|
|
context->tick = value;
|
|
context->tickClock = std::fmod(context->tick, context->ticksPerClock);
|
|
return;
|
|
case kCardinalParameterMiniTimeTicksPerBeat:
|
|
context->ticksPerBeat = value;
|
|
context->ticksPerClock = context->ticksPerBeat / context->beatType;
|
|
context->ticksPerFrame = 1.0 / (60.0 * context->sampleRate / context->beatsPerMinute / context->ticksPerBeat);
|
|
context->tickClock = std::fmod(context->tick, context->ticksPerClock);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void stateChanged(const char* const key, const char* const value) override
|
|
{
|
|
#if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
|
|
if (std::strcmp(key, "patch") == 0)
|
|
{
|
|
if (fAutosavePath.empty())
|
|
return;
|
|
|
|
rack::system::removeRecursively(fAutosavePath);
|
|
rack::system::createDirectories(fAutosavePath);
|
|
|
|
FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
|
|
DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
|
|
|
|
std::fwrite(value, std::strlen(value), 1, f);
|
|
std::fclose(f);
|
|
|
|
const ScopedContext sc(this);
|
|
|
|
try {
|
|
context->patch->loadAutosave();
|
|
} catch(const rack::Exception& e) {
|
|
d_stderr(e.what());
|
|
} DISTRHO_SAFE_EXCEPTION_RETURN("stateChanged loadAutosave",);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (std::strcmp(key, "windowSize") == 0)
|
|
{
|
|
int width = 0;
|
|
int height = 0;
|
|
std::sscanf(value, "%d:%d", &width, &height);
|
|
|
|
if (width > 0 && height > 0)
|
|
{
|
|
const double scaleFactor = getScaleFactor();
|
|
setSize(width * scaleFactor, height * scaleFactor);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
|
|
static int glfwMods(const uint mod) noexcept
|
|
{
|
|
int mods = 0;
|
|
|
|
if (mod & kModifierControl)
|
|
mods |= GLFW_MOD_CONTROL;
|
|
if (mod & kModifierShift)
|
|
mods |= GLFW_MOD_SHIFT;
|
|
if (mod & kModifierAlt)
|
|
mods |= GLFW_MOD_ALT;
|
|
if (mod & kModifierSuper)
|
|
mods |= GLFW_MOD_SUPER;
|
|
|
|
/*
|
|
if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
|
|
mods |= GLFW_MOD_SHIFT;
|
|
if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
|
|
mods |= GLFW_MOD_CONTROL;
|
|
if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
|
|
mods |= GLFW_MOD_ALT;
|
|
if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
|
|
mods |= GLFW_MOD_SUPER;
|
|
*/
|
|
|
|
return mods;
|
|
}
|
|
|
|
bool onMouse(const MouseEvent& ev) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return false;
|
|
#endif
|
|
|
|
if (ev.press)
|
|
getWindow().focus();
|
|
|
|
const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
|
|
int mods = glfwMods(ev.mod);
|
|
|
|
int button;
|
|
|
|
switch (ev.button)
|
|
{
|
|
case kMouseButtonLeft: button = GLFW_MOUSE_BUTTON_LEFT; break;
|
|
case kMouseButtonRight: button = GLFW_MOUSE_BUTTON_RIGHT; break;
|
|
case kMouseButtonMiddle: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
|
|
default:
|
|
button = ev.button;
|
|
break;
|
|
}
|
|
|
|
#ifdef DISTRHO_OS_MAC
|
|
// Remap Ctrl-left click to right click on macOS
|
|
if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
|
|
button = GLFW_MOUSE_BUTTON_RIGHT;
|
|
mods &= ~GLFW_MOD_CONTROL;
|
|
}
|
|
// Remap Ctrl-shift-left click to middle click on macOS
|
|
if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
|
|
button = GLFW_MOUSE_BUTTON_MIDDLE;
|
|
mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
const ScopedContext sc(this, mods);
|
|
return context->event->handleButton(lastMousePos, button, action, mods);
|
|
}
|
|
|
|
bool onMotion(const MotionEvent& ev) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return false;
|
|
#endif
|
|
|
|
const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
|
|
const rack::math::Vec mouseDelta = mousePos.minus(lastMousePos);
|
|
|
|
lastMousePos = mousePos;
|
|
|
|
const ScopedContext sc(this, glfwMods(ev.mod));
|
|
return context->event->handleHover(mousePos, mouseDelta);
|
|
}
|
|
|
|
bool onScroll(const ScrollEvent& ev) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return false;
|
|
#endif
|
|
|
|
const rack::math::Vec scrollDelta = rack::math::Vec(-ev.delta.getX(), ev.delta.getY()) * 50 ;
|
|
|
|
const int mods = glfwMods(ev.mod);
|
|
const ScopedContext sc(this, mods);
|
|
return context->event->handleScroll(lastMousePos, scrollDelta);
|
|
}
|
|
|
|
bool onCharacterInput(const CharacterInputEvent& ev) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return false;
|
|
#endif
|
|
|
|
if (ev.character < ' ' || ev.character >= kKeyDelete)
|
|
return false;
|
|
|
|
const int mods = glfwMods(ev.mod);
|
|
const ScopedContext sc(this, mods);
|
|
return context->event->handleText(lastMousePos, ev.character);
|
|
}
|
|
|
|
bool onKeyboard(const KeyboardEvent& ev) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return false;
|
|
#endif
|
|
|
|
const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
|
|
const int mods = glfwMods(ev.mod);
|
|
|
|
int key;
|
|
switch (ev.key)
|
|
{
|
|
case kKeyTab: key = GLFW_KEY_TAB; break;
|
|
case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
|
|
case kKeyEnter: key = GLFW_KEY_ENTER; break;
|
|
case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
|
|
case kKeyDelete: key = GLFW_KEY_DELETE; break;
|
|
case kKeySpace: key = GLFW_KEY_SPACE; break;
|
|
case kKeyF1: key = GLFW_KEY_F1; break;
|
|
case kKeyF2: key = GLFW_KEY_F2; break;
|
|
case kKeyF3: key = GLFW_KEY_F3; break;
|
|
case kKeyF4: key = GLFW_KEY_F4; break;
|
|
case kKeyF5: key = GLFW_KEY_F5; break;
|
|
case kKeyF6: key = GLFW_KEY_F6; break;
|
|
case kKeyF7: key = GLFW_KEY_F7; break;
|
|
case kKeyF8: key = GLFW_KEY_F8; break;
|
|
case kKeyF9: key = GLFW_KEY_F9; break;
|
|
case kKeyF10: key = GLFW_KEY_F10; break;
|
|
case kKeyF11: key = GLFW_KEY_F11; break;
|
|
case kKeyF12: key = GLFW_KEY_F12; break;
|
|
case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
|
|
case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
|
|
case kKeyEnd: key = GLFW_KEY_END; break;
|
|
case kKeyHome: key = GLFW_KEY_HOME; break;
|
|
case kKeyLeft: key = GLFW_KEY_LEFT; break;
|
|
case kKeyUp: key = GLFW_KEY_UP; break;
|
|
case kKeyRight: key = GLFW_KEY_RIGHT; break;
|
|
case kKeyDown: key = GLFW_KEY_DOWN; break;
|
|
case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
|
|
case kKeyInsert: key = GLFW_KEY_INSERT; break;
|
|
case kKeyPause: key = GLFW_KEY_PAUSE; break;
|
|
case kKeyMenu: key = GLFW_KEY_MENU; break;
|
|
case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
|
|
case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
|
|
case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
|
|
case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
|
|
case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
|
|
case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
|
|
case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
|
|
case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
|
|
case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
|
|
case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
|
|
case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
|
|
case kKeyPad0: key = GLFW_KEY_KP_0; break;
|
|
case kKeyPad1: key = GLFW_KEY_KP_1; break;
|
|
case kKeyPad2: key = GLFW_KEY_KP_2; break;
|
|
case kKeyPad3: key = GLFW_KEY_KP_3; break;
|
|
case kKeyPad4: key = GLFW_KEY_KP_4; break;
|
|
case kKeyPad5: key = GLFW_KEY_KP_5; break;
|
|
case kKeyPad6: key = GLFW_KEY_KP_6; break;
|
|
case kKeyPad7: key = GLFW_KEY_KP_7; break;
|
|
case kKeyPad8: key = GLFW_KEY_KP_8; break;
|
|
case kKeyPad9: key = GLFW_KEY_KP_9; break;
|
|
case kKeyPadEnter: key = GLFW_KEY_KP_ENTER; break;
|
|
/* undefined in glfw
|
|
case kKeyPadPageUp:
|
|
case kKeyPadPageDown:
|
|
case kKeyPadEnd:
|
|
case kKeyPadHome:
|
|
case kKeyPadLeft:
|
|
case kKeyPadUp:
|
|
case kKeyPadRight:
|
|
case kKeyPadDown:
|
|
case kKeyPadClear:
|
|
case kKeyPadInsert:
|
|
case kKeyPadDelete:
|
|
*/
|
|
case kKeyPadEqual: key = GLFW_KEY_KP_EQUAL; break;
|
|
case kKeyPadMultiply: key = GLFW_KEY_KP_MULTIPLY; break;
|
|
case kKeyPadAdd: key = GLFW_KEY_KP_ADD; break;
|
|
/* undefined in glfw
|
|
case kKeyPadSeparator:
|
|
*/
|
|
case kKeyPadSubtract: key = GLFW_KEY_KP_SUBTRACT; break;
|
|
case kKeyPadDecimal: key = GLFW_KEY_KP_DECIMAL; break;
|
|
case kKeyPadDivide: key = GLFW_KEY_KP_DIVIDE; break;
|
|
default:
|
|
// glfw expects uppercase
|
|
if (ev.key >= 'a' && ev.key <= 'z')
|
|
key = ev.key - ('a' - 'A');
|
|
else
|
|
key = ev.key;
|
|
break;
|
|
}
|
|
|
|
const ScopedContext sc(this, mods);
|
|
return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods);
|
|
}
|
|
|
|
void onResize(const ResizeEvent& ev) override
|
|
{
|
|
UI::onResize(ev);
|
|
|
|
if (context->window != nullptr)
|
|
WindowSetInternalSize(context->window, rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
|
|
|
|
const double scaleFactor = getScaleFactor();
|
|
const int width = static_cast<int>(ev.size.getWidth() / scaleFactor + 0.5);
|
|
const int height = static_cast<int>(ev.size.getHeight() / scaleFactor + 0.5);
|
|
|
|
char sizeString[64] = {};
|
|
std::snprintf(sizeString, sizeof(sizeString) - 1, "%d:%d", width, height);
|
|
setState("windowSize", sizeString);
|
|
|
|
if (rack::isStandalone())
|
|
rack::settings::windowSize = rack::math::Vec(width, height);
|
|
}
|
|
|
|
void uiFocus(const bool focus, CrossingMode) override
|
|
{
|
|
#ifdef DPF_RUNTIME_TESTING
|
|
if (inSelfTest) return;
|
|
#endif
|
|
|
|
if (!focus)
|
|
{
|
|
const ScopedContext sc(this, 0);
|
|
context->event->handleLeave();
|
|
}
|
|
}
|
|
|
|
void uiFileBrowserSelected(const char* const filename) override
|
|
{
|
|
if (filename == nullptr)
|
|
return;
|
|
|
|
rack::contextSet(context);
|
|
WindowParametersRestore(context->window);
|
|
|
|
std::string sfilename = filename;
|
|
|
|
if (saving)
|
|
{
|
|
const bool uncompressed = savingUncompressed;
|
|
savingUncompressed = false;
|
|
|
|
if (rack::system::getExtension(sfilename) != ".vcv")
|
|
sfilename += ".vcv";
|
|
|
|
try {
|
|
if (uncompressed)
|
|
{
|
|
context->engine->prepareSave();
|
|
|
|
if (json_t* const rootJ = context->patch->toJson())
|
|
{
|
|
if (FILE* const file = std::fopen(sfilename.c_str(), "w"))
|
|
{
|
|
json_dumpf(rootJ, file, JSON_INDENT(2));
|
|
std::fclose(file);
|
|
}
|
|
json_decref(rootJ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
context->patch->save(sfilename);
|
|
}
|
|
}
|
|
catch (rack::Exception& e) {
|
|
std::string message = rack::string::f("Could not save patch: %s", e.what());
|
|
asyncDialog::create(message.c_str());
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try {
|
|
context->patch->load(sfilename);
|
|
} catch (rack::Exception& e) {
|
|
std::string message = rack::string::f("Could not load patch: %s", e.what());
|
|
asyncDialog::create(message.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
context->patch->path = sfilename;
|
|
context->patch->pushRecentPath(sfilename);
|
|
context->history->setSaved();
|
|
|
|
#ifdef DISTRHO_OS_WASM
|
|
rack::syncfs();
|
|
#else
|
|
rack::settings::save();
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
void uiReshape(const uint width, const uint height) override
|
|
{
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
|
|
glViewport(0, 0, width, height);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
/**
|
|
Set our UI class as non-copyable and add a leak detector just in case.
|
|
*/
|
|
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalUI)
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------
|
|
* UI entry point, called by DPF to create a new UI instance. */
|
|
|
|
UI* createUI()
|
|
{
|
|
return new CardinalUI();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------------
|
|
|
|
END_NAMESPACE_DISTRHO
|