diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp new file mode 100644 index 0000000..7d49e44 --- /dev/null +++ b/include/app/ModuleWidget.hpp @@ -0,0 +1,40 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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. + */ + +#pragma once + +#ifdef BUILDING_PLUGIN_MODULES +# include +# undef ModuleWidget +#endif + +#include_next + +#ifdef BUILDING_PLUGIN_MODULES +namespace rack { +namespace app { +struct CardinalModuleWidget : ModuleWidget { + CardinalModuleWidget() : ModuleWidget() {} + DEPRECATED CardinalModuleWidget(engine::Module* module) : ModuleWidget() { + setModule(module); + } + void onButton(const ButtonEvent& e) override; +}; +} +} +# define ModuleWidget CardinalModuleWidget +#endif diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp new file mode 100644 index 0000000..56c10e6 --- /dev/null +++ b/include/app/RackWidget.hpp @@ -0,0 +1,28 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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. + */ + +#pragma once + +#ifdef BUILDING_PLUGIN_MODULES +# undef ModuleWidget +#endif + +#include_next + +#ifdef BUILDING_PLUGIN_MODULES +# define ModuleWidget CardinalModuleWidget +#endif diff --git a/include/common.hpp b/include/common.hpp index 23b0808..277cb38 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -17,6 +17,7 @@ #pragma once +// check if PRIVATE is defined already before including any headers #ifdef PRIVATE # define PRIVATE_WAS_DEFINED #endif @@ -34,10 +35,12 @@ #define BINARY_END(sym) ((const void*) sym + sym##_len) #define BINARY_SIZE(sym) (sym##_len) +// undefine PRIVATE if needed #if defined(PRIVATE) && !defined(PRIVATE_WAS_DEFINED) # undef PRIVATE #endif +// and also our flag #undef PRIVATE_WAS_DEFINED // Cardinal specific API diff --git a/include/plugin/Model.hpp b/include/plugin/Model.hpp new file mode 100644 index 0000000..83e3a7c --- /dev/null +++ b/include/plugin/Model.hpp @@ -0,0 +1,29 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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. + */ + +#pragma once + +#ifdef BUILDING_PLUGIN_MODULES +namespace rack { +namespace app { +struct CardinalModuleWidget; +} +} +# define ModuleWidget CardinalModuleWidget +#endif + +#include_next diff --git a/plugins/Makefile b/plugins/Makefile index 46e5a4a..794e596 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -658,6 +658,7 @@ else BASE_FLAGS += -DARCH_LIN endif +BASE_FLAGS += -DBUILDING_PLUGIN_MODULES BASE_FLAGS += -fno-finite-math-only BASE_FLAGS += -I../dpf/dgl/src/nanovg BASE_FLAGS += -I../dpf/distrho diff --git a/src/CardinalModuleWidget.cpp b/src/CardinalModuleWidget.cpp new file mode 100644 index 0000000..ed7e4c7 --- /dev/null +++ b/src/CardinalModuleWidget.cpp @@ -0,0 +1,272 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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. + */ + +/** + * This file is partially based on VCVRack's ModuleWidget.cpp + * Copyright (C) 2016-2021 VCV. + * + * 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 (at your option) any later version. + */ + +#define BUILDING_PLUGIN_MODULES +#include +#include +#include +#undef ModuleWidget + +#include +#include +#include +#include +#include +#include + +namespace rack { +namespace app { + +struct ModuleWidget::Internal { + math::Vec dragOffset; + math::Vec dragRackPos; + bool dragEnabled = true; + math::Vec oldPos; + widget::Widget* panel = NULL; +}; + +static void CardinalModuleWidget__loadDialog(ModuleWidget* const w) +{ + std::string presetDir = w->model->getUserPresetDirectory(); + system::createDirectories(presetDir); + + WeakPtr weakThis = w; + + async_dialog_filebrowser(false, presetDir.c_str(), "Load preset", [=](char* pathC) { + // Delete directories if empty + DEFER({ + try { + system::remove(presetDir); + system::remove(system::getDirectory(presetDir)); + } + catch (Exception& e) { + // Ignore exceptions if directory cannot be removed. + } + }); + + if (!weakThis) + return; + if (!pathC) + return; + + try { + weakThis->loadAction(pathC); + } + catch (Exception& e) { + async_dialog_message(e.what()); + } + + std::free(pathC); + }); +} + +void CardinalModuleWidget__saveDialog(ModuleWidget* const w) +{ + const std::string presetDir = w->model->getUserPresetDirectory(); + system::createDirectories(presetDir); + + WeakPtr weakThis = w; + + async_dialog_filebrowser(true, presetDir.c_str(), "Save preset", [=](char* pathC) { + // Delete directories if empty + DEFER({ + try { + system::remove(presetDir); + system::remove(system::getDirectory(presetDir)); + } + catch (Exception& e) { + // Ignore exceptions if directory cannot be removed. + } + }); + + if (!weakThis) + return; + if (!pathC) + return; + + std::string path = pathC; + std::free(pathC); + + // Automatically append .vcvm extension + if (system::getExtension(path) != ".vcvm") + path += ".vcvm"; + + weakThis->save(path); + }); +} + +static void CardinalModuleWidget__createContextMenu(ModuleWidget* const w, + plugin::Model* const model, + engine::Module* const module) { + DISTRHO_SAFE_ASSERT_RETURN(model != nullptr,); + + ui::Menu* menu = createMenu(); + + WeakPtr weakThis = w; + + // Brand and module name + menu->addChild(createMenuLabel(model->name)); + menu->addChild(createMenuLabel(model->plugin->brand)); + + // Info + menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) { + model->appendContextMenu(menu); + })); + + // Preset + menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) { + menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { + if (!weakThis) + return; + weakThis->copyClipboard(); + })); + + menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { + if (!weakThis) + return; + weakThis->pasteClipboardAction(); + })); + + menu->addChild(createMenuItem("Open", "", [=]() { + if (!weakThis) + return; + CardinalModuleWidget__loadDialog(weakThis); + })); + + menu->addChild(createMenuItem("Save as", "", [=]() { + if (!weakThis) + return; + CardinalModuleWidget__saveDialog(weakThis); + })); + + /* TODO + // Scan `/presets//` for presets. + menu->addChild(new ui::MenuSeparator); + menu->addChild(createMenuLabel("User presets")); + appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory()); + + // Scan `/presets/` for presets. + menu->addChild(new ui::MenuSeparator); + menu->addChild(createMenuLabel("Factory presets")); + appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory()); + */ + })); + + // Initialize + menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() { + if (!weakThis) + return; + weakThis->resetAction(); + })); + + // Randomize + menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() { + if (!weakThis) + return; + weakThis->randomizeAction(); + })); + + // Disconnect cables + menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() { + if (!weakThis) + return; + weakThis->disconnectAction(); + })); + + // Bypass + std::string bypassText = RACK_MOD_CTRL_NAME "+E"; + bool bypassed = module && module->isBypassed(); + if (bypassed) + bypassText += " " CHECKMARK_STRING; + menu->addChild(createMenuItem("Bypass", bypassText, [=]() { + if (!weakThis) + return; + weakThis->bypassAction(!bypassed); + })); + + // Duplicate + menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { + if (!weakThis) + return; + weakThis->cloneAction(false); + })); + + // Duplicate with cables + menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { + if (!weakThis) + return; + weakThis->cloneAction(true); + })); + + // Delete + menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { + if (!weakThis) + return; + weakThis->removeAction(); + }, false, true)); + + w->appendContextMenu(menu); +} + +void CardinalModuleWidget::onButton(const ButtonEvent& e) +{ + bool selected = APP->scene->rack->isSelected(this); + + if (selected) { + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { + ui::Menu* menu = createMenu(); + // TODO customize this one too + APP->scene->rack->appendSelectionContextMenu(menu); + } + + e.consume(this); + } + + OpaqueWidget::onButton(e); + + if (e.getTarget() == this) { + // Set starting drag position + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { + internal->dragOffset = e.pos; + } + // Toggle selection on Shift-click + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + APP->scene->rack->select(this, !selected); + } + } + + if (!e.isConsumed() && !selected) { + // Open context menu on right-click + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { + CardinalModuleWidget__createContextMenu(this, model, module); + e.consume(this); + } + } +} + +} +} diff --git a/src/Makefile b/src/Makefile index ebe282d..7cfe855 100644 --- a/src/Makefile +++ b/src/Makefile @@ -87,6 +87,7 @@ BUILD_CXX_FLAGS += -DnsvgParseFromFile=nsvgParseFromFileCardinal # Rack files to build RACK_FILES += AsyncDialog.cpp +RACK_FILES += CardinalModuleWidget.cpp RACK_FILES += override/Engine.cpp RACK_FILES += override/MenuBar.cpp RACK_FILES += override/Scene.cpp