From 810928190c6bbb38d21e6fa52e4a2fcd511af23a Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 23 Jan 2022 17:19:14 +0000 Subject: [PATCH] Add diffs for the overriden files Signed-off-by: falkTX --- src/override/.generate-diffs.sh | 12 + src/override/diffs/Engine.cpp.diff | 761 +++++++++++++++++++ src/override/diffs/MenuBar.cpp.diff | 704 ++++++++++++++++++ src/override/diffs/Model.cpp.diff | 129 ++++ src/override/diffs/Scene.cpp.diff | 234 ++++++ src/override/diffs/Window.cpp.diff | 1052 +++++++++++++++++++++++++++ src/override/diffs/blendish.c.diff | 29 + src/override/diffs/common.cpp.diff | 69 ++ src/override/diffs/context.cpp.diff | 57 ++ 9 files changed, 3047 insertions(+) create mode 100755 src/override/.generate-diffs.sh create mode 100644 src/override/diffs/Engine.cpp.diff create mode 100644 src/override/diffs/MenuBar.cpp.diff create mode 100644 src/override/diffs/Model.cpp.diff create mode 100644 src/override/diffs/Scene.cpp.diff create mode 100644 src/override/diffs/Window.cpp.diff create mode 100644 src/override/diffs/blendish.c.diff create mode 100644 src/override/diffs/common.cpp.diff create mode 100644 src/override/diffs/context.cpp.diff diff --git a/src/override/.generate-diffs.sh b/src/override/.generate-diffs.sh new file mode 100755 index 0000000..e86b20d --- /dev/null +++ b/src/override/.generate-diffs.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +diff -U3 ../Rack/dep/oui-blendish/blendish.c blendish.c > diffs/blendish.c.diff +diff -U3 ../Rack/src/common.cpp common.cpp > diffs/common.cpp.diff +diff -U3 ../Rack/src/context.cpp context.cpp > diffs/context.cpp.diff +diff -U3 ../Rack/src/app/MenuBar.cpp MenuBar.cpp > diffs/MenuBar.cpp.diff +diff -U3 ../Rack/src/app/Scene.cpp Scene.cpp > diffs/Scene.cpp.diff +diff -U3 ../Rack/src/engine/Engine.cpp Engine.cpp > diffs/Engine.cpp.diff +diff -U3 ../Rack/src/plugin/Model.cpp Model.cpp > diffs/Model.cpp.diff +diff -U3 ../Rack/src/window/Window.cpp Window.cpp > diffs/Window.cpp.diff diff --git a/src/override/diffs/Engine.cpp.diff b/src/override/diffs/Engine.cpp.diff new file mode 100644 index 0000000..c560172 --- /dev/null +++ b/src/override/diffs/Engine.cpp.diff @@ -0,0 +1,761 @@ +--- ../Rack/src/engine/Engine.cpp 2022-01-15 14:44:46.395281005 +0000 ++++ Engine.cpp 2022-01-23 17:13:03.200930905 +0000 +@@ -1,3 +1,30 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's engine/Engine.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. ++ */ ++ + #include + #include + #include +@@ -11,178 +38,25 @@ + #include + #include + #include +-#include + #include + #include + #include ++#include ++ ++#ifdef NDEBUG ++# undef DEBUG ++#endif + ++#include "DistrhoUtils.hpp" + + namespace rack { + namespace engine { + + +-static void initMXCSR() { +- // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode +- // https://software.intel.com/en-us/node/682949 +- _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); +- _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); +- // Reset other flags +- _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST); +-} +- +- +-/** Barrier based on mutexes. +-Not finished or tested, do not use. +-*/ +-struct Barrier { +- int count = 0; +- uint8_t step = 0; +- int threads = 0; +- +- std::mutex mutex; +- std::condition_variable cv; +- +- void setThreads(int threads) { +- this->threads = threads; +- } +- +- void wait() { +- std::unique_lock lock(mutex); +- uint8_t s = step; +- if (++count >= threads) { +- // We're the last thread. Reset next phase. +- count = 0; +- // Allow other threads to exit wait() +- step++; +- cv.notify_all(); +- return; +- } +- +- cv.wait(lock, [&] { +- return step != s; +- }); +- } +-}; +- +- +-/** 2-phase barrier based on spin-locking. +-*/ +-struct SpinBarrier { +- std::atomic count{0}; +- std::atomic step{0}; +- int threads = 0; +- +- /** Must be called when no threads are calling wait(). +- */ +- void setThreads(int threads) { +- this->threads = threads; +- } +- +- void wait() { +- uint8_t s = step; +- if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { +- // We're the last thread. Reset next phase. +- count = 0; +- // Allow other threads to exit wait() +- step++; +- return; +- } +- +- // Spin until the last thread begins waiting +- while (true) { +- if (step.load(std::memory_order_relaxed) != s) +- return; +- __builtin_ia32_pause(); +- } +- } +-}; +- +- +-/** Barrier that spin-locks until yield() is called, and then all threads switch to a mutex. +-yield() should be called if it is likely that all threads will block for a while and continuing to spin-lock is unnecessary. +-Saves CPU power after yield is called. +-*/ +-struct HybridBarrier { +- std::atomic count{0}; +- std::atomic step{0}; +- int threads = 0; +- +- std::atomic yielded{false}; +- std::mutex mutex; +- std::condition_variable cv; +- +- void setThreads(int threads) { +- this->threads = threads; +- } +- +- void yield() { +- yielded = true; +- } +- +- void wait() { +- uint8_t s = step; +- if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { +- // We're the last thread. Reset next phase. +- count = 0; +- bool wasYielded = yielded; +- yielded = false; +- // Allow other threads to exit wait() +- step++; +- if (wasYielded) { +- std::unique_lock lock(mutex); +- cv.notify_all(); +- } +- return; +- } +- +- // Spin until the last thread begins waiting +- while (!yielded.load(std::memory_order_relaxed)) { +- if (step.load(std::memory_order_relaxed) != s) +- return; +- __builtin_ia32_pause(); +- } +- +- // Wait on mutex CV +- std::unique_lock lock(mutex); +- cv.wait(lock, [&] { +- return step != s; +- }); +- } +-}; +- +- +-struct EngineWorker { +- Engine* engine; +- int id; +- std::thread thread; +- bool running = false; +- +- void start() { +- assert(!running); +- running = true; +- thread = std::thread([&] { +- run(); +- }); +- } +- +- void requestStop() { +- running = false; +- } +- +- void join() { +- assert(thread.joinable()); +- thread.join(); +- } +- +- void run(); +-}; +- +- + struct Engine::Internal { + std::vector modules; + std::vector cables; + std::set paramHandles; +- Module* masterModule = NULL; + + // moduleId + std::map modulesCache; +@@ -217,22 +91,6 @@ + Readers lock when using the engine's state. + */ + SharedMutex mutex; +- /** Mutex that guards stepBlock() so it's not called simultaneously. +- */ +- std::mutex blockMutex; +- +- int threadCount = 0; +- std::vector workers; +- HybridBarrier engineBarrier; +- HybridBarrier workerBarrier; +- std::atomic workerModuleIndex; +- // For worker threads +- Context* context; +- +- bool fallbackRunning = false; +- std::thread fallbackThread; +- std::mutex fallbackMutex; +- std::condition_variable fallbackCv; + }; + + +@@ -260,71 +118,6 @@ + } + + +-static void Engine_relaunchWorkers(Engine* that, int threadCount) { +- Engine::Internal* internal = that->internal; +- if (threadCount == internal->threadCount) +- return; +- +- if (internal->threadCount > 0) { +- // Stop engine workers +- for (EngineWorker& worker : internal->workers) { +- worker.requestStop(); +- } +- internal->engineBarrier.wait(); +- +- // Join and destroy engine workers +- for (EngineWorker& worker : internal->workers) { +- worker.join(); +- } +- internal->workers.resize(0); +- } +- +- // Configure engine +- internal->threadCount = threadCount; +- +- // Set barrier counts +- internal->engineBarrier.setThreads(threadCount); +- internal->workerBarrier.setThreads(threadCount); +- +- if (threadCount > 0) { +- // Create and start engine workers +- internal->workers.resize(threadCount - 1); +- for (int id = 1; id < threadCount; id++) { +- EngineWorker& worker = internal->workers[id - 1]; +- worker.id = id; +- worker.engine = that; +- worker.start(); +- } +- } +-} +- +- +-static void Engine_stepWorker(Engine* that, int threadId) { +- Engine::Internal* internal = that->internal; +- +- // int threadCount = internal->threadCount; +- int modulesLen = internal->modules.size(); +- +- // Build ProcessArgs +- Module::ProcessArgs processArgs; +- processArgs.sampleRate = internal->sampleRate; +- processArgs.sampleTime = internal->sampleTime; +- processArgs.frame = internal->frame; +- +- // Step each module +- while (true) { +- // Choose next module +- // First-come-first serve module-to-thread allocation algorithm +- int i = internal->workerModuleIndex++; +- if (i >= modulesLen) +- break; +- +- Module* module = internal->modules[i]; +- module->doProcess(processArgs); +- } +-} +- +- + static void Cable_step(Cable* that) { + Output* output = &that->outputModule->outputs[that->outputId]; + Input* input = &that->inputModule->inputs[that->inputId]; +@@ -373,12 +166,12 @@ + } + + // Step cables +- for (Cable* cable : that->internal->cables) { ++ for (Cable* cable : internal->cables) { + Cable_step(cable); + } + + // Flip messages for each module +- for (Module* module : that->internal->modules) { ++ for (Module* module : internal->modules) { + if (module->leftExpander.messageFlipRequested) { + std::swap(module->leftExpander.producerMessage, module->leftExpander.consumerMessage); + module->leftExpander.messageFlipRequested = false; +@@ -389,13 +182,18 @@ + } + } + +- // Step modules along with workers +- internal->workerModuleIndex = 0; +- internal->engineBarrier.wait(); +- Engine_stepWorker(that, 0); +- internal->workerBarrier.wait(); ++ // Build ProcessArgs ++ Module::ProcessArgs processArgs; ++ processArgs.sampleRate = internal->sampleRate; ++ processArgs.sampleTime = internal->sampleTime; ++ processArgs.frame = internal->frame; ++ ++ // Step each module ++ for (Module* module : internal->modules) { ++ module->doProcess(processArgs); ++ } + +- internal->frame++; ++ ++internal->frame; + } + + +@@ -460,37 +258,22 @@ + + Engine::Engine() { + internal = new Internal; +- +- internal->context = contextGet(); +- setSuggestedSampleRate(0.f); + } + + + Engine::~Engine() { +- // Stop fallback thread if running +- { +- std::lock_guard lock(internal->fallbackMutex); +- internal->fallbackRunning = false; +- internal->fallbackCv.notify_all(); +- } +- if (internal->fallbackThread.joinable()) +- internal->fallbackThread.join(); +- +- // Shut down workers +- Engine_relaunchWorkers(this, 0); +- + // Clear modules, cables, etc + clear(); + + // Make sure there are no cables or modules in the rack on destruction. + // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. +- assert(internal->cables.empty()); +- assert(internal->modules.empty()); +- assert(internal->paramHandles.empty()); +- +- assert(internal->modulesCache.empty()); +- assert(internal->cablesCache.empty()); +- assert(internal->paramHandlesCache.empty()); ++ DISTRHO_SAFE_ASSERT(internal->cables.empty()); ++ DISTRHO_SAFE_ASSERT(internal->modules.empty()); ++ DISTRHO_SAFE_ASSERT(internal->paramHandles.empty()); ++ ++ DISTRHO_SAFE_ASSERT(internal->modulesCache.empty()); ++ DISTRHO_SAFE_ASSERT(internal->cablesCache.empty()); ++ DISTRHO_SAFE_ASSERT(internal->paramHandlesCache.empty()); + + delete internal; + } +@@ -526,11 +309,8 @@ + // Start timer before locking + double startTime = system::getTime(); + +- std::lock_guard stepLock(internal->blockMutex); + SharedLock lock(internal->mutex); + // Configure thread +- uint32_t csr = _mm_getcsr(); +- initMXCSR(); + random::init(); + + internal->blockFrame = internal->frame; +@@ -543,16 +323,11 @@ + Engine_updateExpander_NoLock(this, module, true); + } + +- // Launch workers +- Engine_relaunchWorkers(this, settings::threadCount); +- + // Step individual frames + for (int i = 0; i < frames; i++) { + Engine_stepFrame(this); + } + +- yieldWorkers(); +- + internal->block++; + + // Stop timer +@@ -572,47 +347,19 @@ + internal->meterTotal = 0.0; + internal->meterMax = 0.0; + } +- +- // Reset MXCSR back to original value +- _mm_setcsr(csr); + } + + + void Engine::setMasterModule(Module* module) { +- if (module == internal->masterModule) +- return; +- std::lock_guard lock(internal->mutex); +- setMasterModule_NoLock(module); + } + + + void Engine::setMasterModule_NoLock(Module* module) { +- if (module == internal->masterModule) +- return; +- +- if (internal->masterModule) { +- // Dispatch UnsetMasterEvent +- Module::UnsetMasterEvent e; +- internal->masterModule->onUnsetMaster(e); +- } +- +- internal->masterModule = module; +- +- if (internal->masterModule) { +- // Dispatch SetMasterEvent +- Module::SetMasterEvent e; +- internal->masterModule->onSetMaster(e); +- } +- +- // Wake up fallback thread if master module was unset +- if (!internal->masterModule) { +- internal->fallbackCv.notify_all(); +- } + } + + + Module* Engine::getMasterModule() { +- return internal->masterModule; ++ return nullptr; + } + + +@@ -639,16 +386,6 @@ + + + void Engine::setSuggestedSampleRate(float suggestedSampleRate) { +- if (settings::sampleRate > 0) { +- setSampleRate(settings::sampleRate); +- } +- else if (suggestedSampleRate > 0) { +- setSampleRate(suggestedSampleRate); +- } +- else { +- // Fallback sample rate +- setSampleRate(44100.f); +- } + } + + +@@ -658,7 +395,6 @@ + + + void Engine::yieldWorkers() { +- internal->workerBarrier.yield(); + } + + +@@ -738,10 +474,10 @@ + + void Engine::addModule(Module* module) { + std::lock_guard lock(internal->mutex); +- assert(module); ++ DISTRHO_SAFE_ASSERT_RETURN(module,); + // Check that the module is not already added + auto it = std::find(internal->modules.begin(), internal->modules.end(), module); +- assert(it == internal->modules.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it == internal->modules.end(),); + // Set ID if unset or collides with an existing ID + while (module->id < 0 || internal->modulesCache.find(module->id) != internal->modulesCache.end()) { + // Randomly generate ID +@@ -773,10 +509,14 @@ + + + void Engine::removeModule_NoLock(Module* module) { +- assert(module); ++ DISTRHO_SAFE_ASSERT_RETURN(module,); + // Check that the module actually exists + auto it = std::find(internal->modules.begin(), internal->modules.end(), module); +- assert(it != internal->modules.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),); ++ // Remove from widgets cache ++ CardinalPluginModelHelper* const helper = dynamic_cast(module->model); ++ DISTRHO_SAFE_ASSERT_RETURN(helper != nullptr,); ++ helper->removeCachedModuleWidget(module); + // Dispatch RemoveEvent + Module::RemoveEvent eRemove; + module->onRemove(eRemove); +@@ -785,18 +525,14 @@ + if (paramHandle->moduleId == module->id) + paramHandle->module = NULL; + } +- // Unset master module +- if (getMasterModule() == module) { +- setMasterModule_NoLock(NULL); +- } + // If a param is being smoothed on this module, stop smoothing it immediately + if (module == internal->smoothModule) { + internal->smoothModule = NULL; + } + // Check that all cables are disconnected + for (Cable* cable : internal->cables) { +- assert(cable->inputModule != module); +- assert(cable->outputModule != module); ++ DISTRHO_SAFE_ASSERT(cable->inputModule != module); ++ DISTRHO_SAFE_ASSERT(cable->outputModule != module); + } + // Update expanders of other modules + for (Module* m : internal->modules) { +@@ -844,7 +580,7 @@ + + void Engine::resetModule(Module* module) { + std::lock_guard lock(internal->mutex); +- assert(module); ++ DISTRHO_SAFE_ASSERT_RETURN(module,); + + Module::ResetEvent eReset; + module->onReset(eReset); +@@ -853,7 +589,7 @@ + + void Engine::randomizeModule(Module* module) { + std::lock_guard lock(internal->mutex); +- assert(module); ++ DISTRHO_SAFE_ASSERT_RETURN(module,); + + Module::RandomizeEvent eRandomize; + module->onRandomize(eRandomize); +@@ -861,7 +597,7 @@ + + + void Engine::bypassModule(Module* module, bool bypassed) { +- assert(module); ++ DISTRHO_SAFE_ASSERT_RETURN(module,); + if (module->isBypassed() == bypassed) + return; + +@@ -946,16 +682,16 @@ + + void Engine::addCable(Cable* cable) { + std::lock_guard lock(internal->mutex); +- assert(cable); ++ DISTRHO_SAFE_ASSERT_RETURN(cable,); + // Check cable properties +- assert(cable->inputModule); +- assert(cable->outputModule); ++ DISTRHO_SAFE_ASSERT_RETURN(cable->inputModule,); ++ DISTRHO_SAFE_ASSERT_RETURN(cable->outputModule,); + bool outputWasConnected = false; + for (Cable* cable2 : internal->cables) { + // Check that the cable is not already added +- assert(cable2 != cable); ++ DISTRHO_SAFE_ASSERT_RETURN(cable2 != cable,); + // Check that the input is not already used by another cable +- assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); ++ DISTRHO_SAFE_ASSERT_RETURN(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId),); + // Get connected status of output, to decide whether we need to call a PortChangeEvent. + // It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()` + if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) +@@ -996,10 +732,10 @@ + + + void Engine::removeCable_NoLock(Cable* cable) { +- assert(cable); ++ DISTRHO_SAFE_ASSERT_RETURN(cable,); + // Check that the cable is already added + auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); +- assert(it != internal->cables.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it != internal->cables.end(),); + // Remove the cable + internal->cablesCache.erase(cable->id); + internal->cables.erase(it); +@@ -1085,11 +821,11 @@ + std::lock_guard lock(internal->mutex); + // New ParamHandles must be blank. + // This means we don't have to refresh the cache. +- assert(paramHandle->moduleId < 0); ++ DISTRHO_SAFE_ASSERT_RETURN(paramHandle->moduleId < 0,); + + // Check that the ParamHandle is not already added + auto it = internal->paramHandles.find(paramHandle); +- assert(it == internal->paramHandles.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it == internal->paramHandles.end(),); + + // Add it + internal->paramHandles.insert(paramHandle); +@@ -1106,7 +842,7 @@ + void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) { + // Check that the ParamHandle is already added + auto it = internal->paramHandles.find(paramHandle); +- assert(it != internal->paramHandles.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); + + // Remove it + paramHandle->module = NULL; +@@ -1143,7 +879,7 @@ + void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { + // Check that it exists + auto it = internal->paramHandles.find(paramHandle); +- assert(it != internal->paramHandles.end()); ++ DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); + + // Set IDs + paramHandle->moduleId = moduleId; +@@ -1197,11 +933,6 @@ + } + json_object_set_new(rootJ, "cables", cablesJ); + +- // masterModule +- if (internal->masterModule) { +- json_object_set_new(rootJ, "masterModuleId", json_integer(internal->masterModule->id)); +- } +- + return rootJ; + } + +@@ -1225,14 +956,20 @@ + } + catch (Exception& e) { + WARN("Cannot load model: %s", e.what()); +- APP->patch->log(e.what()); ++ // APP->patch->log(e.what()); + continue; + } + + // Create module +- INFO("Creating module %s", model->getFullName().c_str()); +- Module* module = model->createModule(); +- assert(module); ++ Module* const module = model->createModule(); ++ DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr); ++ ++ // Create the widget too, needed by a few modules ++ CardinalPluginModelHelper* const helper = dynamic_cast(model); ++ DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr); ++ ++ app::ModuleWidget* const moduleWidget = helper->createModuleWidgetFromEngineLoad(module); ++ DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr); + + try { + // This doesn't need a lock because the Module is not added to the Engine yet. +@@ -1248,7 +985,8 @@ + } + catch (Exception& e) { + WARN("Cannot load module: %s", e.what()); +- APP->patch->log(e.what()); ++ // APP->patch->log(e.what()); ++ helper->removeCachedModuleWidget(module); + delete module; + continue; + } +@@ -1285,67 +1023,10 @@ + continue; + } + } +- +- // masterModule +- json_t* masterModuleIdJ = json_object_get(rootJ, "masterModuleId"); +- if (masterModuleIdJ) { +- Module* masterModule = getModule(json_integer_value(masterModuleIdJ)); +- setMasterModule(masterModule); +- } +-} +- +- +-void EngineWorker::run() { +- // Configure thread +- contextSet(engine->internal->context); +- system::setThreadName(string::f("Worker %d", id)); +- initMXCSR(); +- random::init(); +- +- while (true) { +- engine->internal->engineBarrier.wait(); +- if (!running) +- return; +- Engine_stepWorker(engine, id); +- engine->internal->workerBarrier.wait(); +- } +-} +- +- +-static void Engine_fallbackRun(Engine* that) { +- system::setThreadName("Engine fallback"); +- contextSet(that->internal->context); +- +- while (that->internal->fallbackRunning) { +- if (!that->getMasterModule()) { +- // Step blocks and wait +- double start = system::getTime(); +- int frames = std::floor(that->getSampleRate() / 60); +- that->stepBlock(frames); +- double end = system::getTime(); +- +- double duration = frames * that->getSampleTime() - (end - start); +- if (duration > 0.0) { +- std::this_thread::sleep_for(std::chrono::duration(duration)); +- } +- } +- else { +- // Wait for master module to be unset, or for the request to stop running +- std::unique_lock lock(that->internal->fallbackMutex); +- that->internal->fallbackCv.wait(lock, [&]() { +- return !that->internal->fallbackRunning || !that->getMasterModule(); +- }); +- } +- } + } + + + void Engine::startFallbackThread() { +- if (internal->fallbackThread.joinable()) +- return; +- +- internal->fallbackRunning = true; +- internal->fallbackThread = std::thread(Engine_fallbackRun, this); + } + + diff --git a/src/override/diffs/MenuBar.cpp.diff b/src/override/diffs/MenuBar.cpp.diff new file mode 100644 index 0000000..43b1057 --- /dev/null +++ b/src/override/diffs/MenuBar.cpp.diff @@ -0,0 +1,704 @@ +--- ../Rack/src/app/MenuBar.cpp 2022-01-15 14:44:46.391280963 +0000 ++++ MenuBar.cpp 2022-01-23 17:13:16.500279828 +0000 +@@ -1,8 +1,33 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's app/MenuBar.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. ++ */ ++ + #include + #include + +-#include +- + #include + #include + #include +@@ -25,6 +50,11 @@ + #include + #include + ++#ifdef HAVE_LIBLO ++# include ++#endif ++ ++#include "../CardinalCommon.hpp" + + namespace rack { + namespace app { +@@ -48,80 +78,108 @@ + }; + + +-struct NotificationIcon : widget::Widget { +- void draw(const DrawArgs& args) override { +- nvgBeginPath(args.vg); +- float radius = 4; +- nvgCircle(args.vg, radius, radius, radius); +- nvgFillColor(args.vg, nvgRGBf(1.0, 0.0, 0.0)); +- nvgFill(args.vg); +- nvgStrokeColor(args.vg, nvgRGBf(0.5, 0.0, 0.0)); +- nvgStroke(args.vg); +- } +-}; +- +- + //////////////////// + // File + //////////////////// + + + struct FileButton : MenuButton { ++ const bool isStandalone; ++ ++#ifdef HAVE_LIBLO ++ bool oscConnected = false; ++ lo_server oscServer = nullptr; ++ ++ static int osc_handler(const char* const path, const char* const types, lo_arg** argv, const int argc, lo_message, void* const self) ++ { ++ d_stdout("osc_handler(\"%s\", \"%s\", %p, %i)", path, types, argv, argc); ++ ++ if (std::strcmp(path, "/resp") == 0 && argc == 2 && types[0] == 's' && types[1] == 's') { ++ d_stdout("osc_handler(\"%s\", ...) - got resp | '%s' '%s'", path, &argv[0]->s, &argv[1]->s); ++ if (std::strcmp(&argv[0]->s, "hello") == 0 && std::strcmp(&argv[1]->s, "ok") == 0) ++ static_cast(self)->oscConnected = true; ++ } ++ return 0; ++ } ++ ++ ~FileButton() { ++ lo_server_free(oscServer); ++ } ++#endif ++ ++ FileButton(const bool standalone) ++ : MenuButton(), isStandalone(standalone) {} ++ + void onAction(const ActionEvent& e) override { + ui::Menu* menu = createMenu(); + menu->cornerFlags = BND_CORNER_TOP; + menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); + + menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { +- APP->patch->loadTemplateDialog(); ++ patchUtils::loadTemplateDialog(); + })); + +- menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", []() { +- APP->patch->loadDialog(); ++ menu->addChild(createMenuItem("Open / Import...", RACK_MOD_CTRL_NAME "+O", []() { ++ patchUtils::loadDialog(); + })); + +- menu->addChild(createSubmenuItem("Open recent", "", [](ui::Menu* menu) { +- for (const std::string& path : settings::recentPatchPaths) { +- std::string name = system::getStem(path); +- menu->addChild(createMenuItem(name, "", [=]() { +- APP->patch->loadPathDialog(path); +- })); +- } +- }, settings::recentPatchPaths.empty())); +- + menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { +- APP->patch->saveDialog(); +- })); +- +- menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { +- APP->patch->saveAsDialog(); ++ // NOTE: will do nothing if path is empty, intentionally ++ patchUtils::saveDialog(APP->patch->path); ++ }, APP->patch->path.empty())); ++ ++ menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() { ++ patchUtils::saveAsDialog(); + })); + +- menu->addChild(createMenuItem("Save a copy", "", []() { +- APP->patch->saveAsDialog(false); +- })); ++#ifdef HAVE_LIBLO ++ if (oscServer == nullptr || !oscConnected) { ++ menu->addChild(createMenuItem("Connect to MOD", "", [this]() { ++ if (oscServer == nullptr) { ++ oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr); ++ DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr,); ++ lo_server_add_method(oscServer, "/resp", nullptr, osc_handler, this); ++ } ++ const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT); ++ DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,); ++ lo_send(addr, "/hello", ""); ++ lo_address_free(addr); ++ })); ++ } else { ++ menu->addChild(createMenuItem("Deploy to MOD", "F7", []() { ++ patchUtils::deployToMOD(); ++ })); ++ } ++#endif + + menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { +- APP->patch->revertDialog(); +- }, APP->patch->path == "")); +- +- menu->addChild(createMenuItem("Overwrite template", "", []() { +- APP->patch->saveTemplateDialog(); +- })); ++ patchUtils::revertDialog(); ++ }, APP->patch->path.empty())); + + menu->addChild(new ui::MenuSeparator); + + // Load selection + menu->addChild(createMenuItem("Import selection", "", [=]() { +- APP->scene->rack->loadSelectionDialog(); ++ patchUtils::loadSelectionDialog(); + }, false, true)); + +- menu->addChild(new ui::MenuSeparator); ++ if (isStandalone) { ++ menu->addChild(new ui::MenuSeparator); + +- menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { +- APP->window->close(); +- })); ++ menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { ++ APP->window->close(); ++ })); ++ }; + } ++ ++#ifdef HAVE_LIBLO ++ void step() override { ++ MenuButton::step(); ++ if (oscServer != nullptr) { ++ while (lo_server_recv_noblock(oscServer, 0) != 0) {} ++ } ++ } ++#endif + }; + + +@@ -166,7 +224,7 @@ + + menu->addChild(new ui::MenuSeparator); + +- APP->scene->rack->appendSelectionContextMenu(menu); ++ patchUtils::appendSelectionContextMenu(menu); + } + }; + +@@ -256,7 +314,7 @@ + return settings::cableTension; + } + float getDefaultValue() override { +- return 0.5; ++ return 0.75; + } + float getDisplayValue() override { + return getValue() * 100; +@@ -421,28 +479,9 @@ + haloBrightnessSlider->box.size.x = 250.0; + menu->addChild(haloBrightnessSlider); + +- double frameRate = APP->window->getMonitorRefreshRate() / settings::frameSwapInterval; +- menu->addChild(createSubmenuItem("Frame rate", string::f("%.0f Hz", frameRate), [=](ui::Menu* menu) { +- for (int i = 1; i <= 6; i++) { +- double frameRate = APP->window->getMonitorRefreshRate() / i; +- menu->addChild(createCheckMenuItem(string::f("%.0f Hz", frameRate), "", +- [=]() {return settings::frameSwapInterval == i;}, +- [=]() {settings::frameSwapInterval = i;} +- )); +- } +- })); +- +- bool fullscreen = APP->window->isFullScreen(); +- std::string fullscreenText = "F11"; +- if (fullscreen) +- fullscreenText += " " CHECKMARK_STRING; +- menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() { +- APP->window->setFullScreen(!fullscreen); +- })); +- + menu->addChild(new ui::MenuSeparator); + +- menu->addChild(createBoolPtrMenuItem("Lock cursor while dragging params", "", &settings::allowCursorLock)); ++ // menu->addChild(createBoolPtrMenuItem("Hide cursor while dragging", "", &settings::allowCursorLock)); + + static const std::vector knobModeLabels = { + "Linear", +@@ -467,6 +506,21 @@ + menu->addChild(knobScrollSensitivitySlider); + + menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules)); ++ ++ static const std::vector rateLimitLabels = { ++ "None", ++ "2x", ++ "4x", ++ }; ++ static const std::vector rateLimits = {0, 1, 2}; ++ menu->addChild(createSubmenuItem("Update rate limit", rateLimitLabels[settings::rateLimit], [=](ui::Menu* menu) { ++ for (int rateLimit : rateLimits) { ++ menu->addChild(createCheckMenuItem(rateLimitLabels[rateLimit], "", ++ [=]() {return settings::rateLimit == rateLimit;}, ++ [=]() {settings::rateLimit = rateLimit;} ++ )); ++ } ++ })); + } + }; + +@@ -476,47 +530,6 @@ + //////////////////// + + +-struct SampleRateItem : ui::MenuItem { +- ui::Menu* createChildMenu() override { +- ui::Menu* menu = new ui::Menu; +- +- // Auto sample rate +- std::string rightText; +- if (settings::sampleRate == 0) { +- float sampleRate = APP->engine->getSampleRate(); +- rightText += string::f("(%g kHz) ", sampleRate / 1000.f); +- } +- menu->addChild(createCheckMenuItem("Auto", rightText, +- [=]() {return settings::sampleRate == 0;}, +- [=]() {settings::sampleRate = 0;} +- )); +- +- // Power-of-2 oversample times 44.1kHz or 48kHz +- for (int i = -2; i <= 4; i++) { +- for (int j = 0; j < 2; j++) { +- float oversample = std::pow(2.f, i); +- float sampleRate = (j == 0) ? 44100.f : 48000.f; +- sampleRate *= oversample; +- +- std::string text = string::f("%g kHz", sampleRate / 1000.f); +- std::string rightText; +- if (oversample > 1.f) { +- rightText += string::f("(%.0fx)", oversample); +- } +- else if (oversample < 1.f) { +- rightText += string::f("(1/%.0fx)", 1.f / oversample); +- } +- menu->addChild(createCheckMenuItem(text, rightText, +- [=]() {return settings::sampleRate == sampleRate;}, +- [=]() {settings::sampleRate = sampleRate;} +- )); +- } +- } +- return menu; +- } +-}; +- +- + struct EngineButton : MenuButton { + void onAction(const ActionEvent& e) override { + ui::Menu* menu = createMenu(); +@@ -529,269 +542,6 @@ + menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() { + settings::cpuMeter ^= true; + })); +- +- menu->addChild(createMenuItem("Sample rate", RIGHT_ARROW)); +- +- menu->addChild(createSubmenuItem("Threads", string::f("%d", settings::threadCount), [=](ui::Menu* menu) { +- // BUG This assumes SMT is enabled. +- int cores = system::getLogicalCoreCount() / 2; +- +- for (int i = 1; i <= 2 * cores; i++) { +- std::string rightText; +- if (i == cores) +- rightText += "(most modules)"; +- else if (i == 1) +- rightText += "(lowest CPU usage)"; +- menu->addChild(createCheckMenuItem(string::f("%d", i), rightText, +- [=]() {return settings::threadCount == i;}, +- [=]() {settings::threadCount = i;} +- )); +- } +- })); +- } +-}; +- +- +-//////////////////// +-// Plugins +-//////////////////// +- +- +-struct AccountPasswordField : ui::PasswordField { +- ui::MenuItem* logInItem; +- void onAction(const ActionEvent& e) override { +- logInItem->doAction(); +- } +-}; +- +- +-struct LogInItem : ui::MenuItem { +- ui::TextField* emailField; +- ui::TextField* passwordField; +- +- void onAction(const ActionEvent& e) override { +- std::string email = emailField->text; +- std::string password = passwordField->text; +- std::thread t([=] { +- library::logIn(email, password); +- library::checkUpdates(); +- }); +- t.detach(); +- e.unconsume(); +- } +- +- void step() override { +- text = "Log in"; +- rightText = library::loginStatus; +- MenuItem::step(); +- } +-}; +- +- +-struct SyncUpdatesItem : ui::MenuItem { +- void step() override { +- if (library::updateStatus != "") { +- text = library::updateStatus; +- } +- else if (library::isSyncing) { +- text = "Updating..."; +- } +- else if (!library::hasUpdates()) { +- text = "Up-to-date"; +- } +- else { +- text = "Update all"; +- } +- +- disabled = library::isSyncing || !library::hasUpdates(); +- MenuItem::step(); +- } +- +- void onAction(const ActionEvent& e) override { +- std::thread t([=] { +- library::syncUpdates(); +- }); +- t.detach(); +- e.unconsume(); +- } +-}; +- +- +-struct SyncUpdateItem : ui::MenuItem { +- std::string slug; +- +- void setUpdate(const std::string& slug) { +- this->slug = slug; +- +- auto it = library::updateInfos.find(slug); +- if (it == library::updateInfos.end()) +- return; +- library::UpdateInfo update = it->second; +- +- text = update.name; +- } +- +- ui::Menu* createChildMenu() override { +- auto it = library::updateInfos.find(slug); +- if (it == library::updateInfos.end()) +- return NULL; +- library::UpdateInfo update = it->second; +- +- if (update.changelogUrl == "") +- return NULL; +- +- ui::Menu* menu = new ui::Menu; +- +- std::string changelogUrl = update.changelogUrl; +- menu->addChild(createMenuItem("Changelog", "", [=]() { +- system::openBrowser(changelogUrl); +- })); +- +- return menu; +- } +- +- void step() override { +- disabled = library::isSyncing; +- +- auto it = library::updateInfos.find(slug); +- if (it != library::updateInfos.end()) { +- library::UpdateInfo update = it->second; +- +- if (update.downloaded) { +- rightText = CHECKMARK_STRING; +- disabled = true; +- } +- else if (slug == library::updateSlug) { +- rightText = string::f("%.0f%%", library::updateProgress * 100.f); +- } +- else { +- rightText = ""; +- plugin::Plugin* p = plugin::getPlugin(slug); +- if (p) { +- rightText += p->version + " → "; +- } +- rightText += update.version; +- } +- } +- +- MenuItem::step(); +- } +- +- void onAction(const ActionEvent& e) override { +- std::thread t([=] { +- library::syncUpdate(slug); +- }); +- t.detach(); +- e.unconsume(); +- } +-}; +- +- +-struct LibraryMenu : ui::Menu { +- LibraryMenu() { +- refresh(); +- } +- +- void step() override { +- // Refresh menu when appropriate +- if (library::refreshRequested) { +- library::refreshRequested = false; +- refresh(); +- } +- Menu::step(); +- } +- +- void refresh() { +- setChildMenu(NULL); +- clearChildren(); +- +- if (settings::devMode) { +- addChild(createMenuLabel("Disabled in development mode")); +- } +- else if (!library::isLoggedIn()) { +- addChild(createMenuItem("Register VCV account", "", [=]() { +- system::openBrowser("https://vcvrack.com/login"); +- })); +- +- ui::TextField* emailField = new ui::TextField; +- emailField->placeholder = "Email"; +- emailField->box.size.x = 240.0; +- addChild(emailField); +- +- AccountPasswordField* passwordField = new AccountPasswordField; +- passwordField->placeholder = "Password"; +- passwordField->box.size.x = 240.0; +- passwordField->nextField = emailField; +- emailField->nextField = passwordField; +- addChild(passwordField); +- +- LogInItem* logInItem = new LogInItem; +- logInItem->emailField = emailField; +- logInItem->passwordField = passwordField; +- passwordField->logInItem = logInItem; +- addChild(logInItem); +- } +- else { +- addChild(createMenuItem("Log out", "", [=]() { +- library::logOut(); +- })); +- +- addChild(createMenuItem("Browse VCV Library", "", [=]() { +- system::openBrowser("https://library.vcvrack.com/"); +- })); +- +- SyncUpdatesItem* syncItem = new SyncUpdatesItem; +- syncItem->text = "Update all"; +- addChild(syncItem); +- +- if (!library::updateInfos.empty()) { +- addChild(new ui::MenuSeparator); +- addChild(createMenuLabel("Updates")); +- +- for (auto& pair : library::updateInfos) { +- SyncUpdateItem* updateItem = new SyncUpdateItem; +- updateItem->setUpdate(pair.first); +- addChild(updateItem); +- } +- } +- } +- } +-}; +- +- +-struct LibraryButton : MenuButton { +- NotificationIcon* notification; +- +- LibraryButton() { +- notification = new NotificationIcon; +- addChild(notification); +- } +- +- void onAction(const ActionEvent& e) override { +- ui::Menu* menu = createMenu(); +- menu->cornerFlags = BND_CORNER_TOP; +- menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); +- // Check for updates when menu is opened +- std::thread t([&]() { +- system::setThreadName("Library"); +- library::checkUpdates(); +- }); +- t.detach(); +- } +- +- void step() override { +- notification->box.pos = math::Vec(0, 0); +- notification->visible = library::hasUpdates(); +- +- // Popup when updates finish downloading +- if (library::restartRequested) { +- library::restartRequested = false; +- if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) { +- APP->window->close(); +- } +- } +- +- MenuButton::step(); + } + }; + +@@ -802,63 +552,24 @@ + + + struct HelpButton : MenuButton { +- NotificationIcon* notification; +- +- HelpButton() { +- notification = new NotificationIcon; +- addChild(notification); +- } +- + void onAction(const ActionEvent& e) override { + ui::Menu* menu = createMenu(); + menu->cornerFlags = BND_CORNER_TOP; + menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); + +- menu->addChild(createMenuItem("Tips", "", [=]() { +- APP->scene->addChild(tipWindowCreate()); +- })); +- +- menu->addChild(createMenuItem("User manual", "F1", [=]() { ++ menu->addChild(createMenuItem("Rack User manual", "F1", [=]() { + system::openBrowser("https://vcvrack.com/manual/"); + })); + +- menu->addChild(createMenuItem("VCVRack.com", "", [=]() { +- system::openBrowser("https://vcvrack.com/"); +- })); +- +- menu->addChild(createMenuItem("Open user folder", "", [=]() { +- system::openDirectory(asset::user("")); ++ menu->addChild(createMenuItem("Cardinal Project page", "", [=]() { ++ system::openBrowser("https://github.com/DISTRHO/Cardinal/"); + })); + +- if (library::isAppUpdateAvailable()) { +- menu->addChild(new ui::MenuSeparator); +- +- menu->addChild(createMenuItem("Update " + APP_NAME, APP_VERSION + " → " + library::appVersion, [=]() { +- system::openBrowser(library::appDownloadUrl); +- })); +- +- menu->addChild(createMenuItem("Review changelog", "", [=]() { +- system::openBrowser(library::appChangelogUrl); +- })); +- } +- else if (!settings::autoCheckUpdates && !settings::devMode) { +- menu->addChild(createMenuItem("Check for " + APP_NAME + " update", "", [=]() { +- std::thread t([&]() { +- library::checkAppUpdate(); +- }); +- t.detach(); +- }, false, true)); +- } +- + menu->addChild(new ui::MenuSeparator); + +- menu->addChild(createMenuLabel(APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION)); +- } ++ menu->addChild(createMenuLabel(APP_EDITION + " " + APP_EDITION_NAME)); + +- void step() override { +- notification->box.pos = math::Vec(0, 0); +- notification->visible = library::isAppUpdateAvailable(); +- MenuButton::step(); ++ menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible")); + } + }; + +@@ -908,7 +619,9 @@ + struct MenuBar : widget::OpaqueWidget { + MeterLabel* meterLabel; + +- MenuBar() { ++ MenuBar(const bool isStandalone) ++ : widget::OpaqueWidget() ++ { + const float margin = 5; + box.size.y = BND_WIDGET_HEIGHT + 2 * margin; + +@@ -917,7 +630,7 @@ + layout->spacing = math::Vec(0, 0); + addChild(layout); + +- FileButton* fileButton = new FileButton; ++ FileButton* fileButton = new FileButton(isStandalone); + fileButton->text = "File"; + layout->addChild(fileButton); + +@@ -933,10 +646,6 @@ + engineButton->text = "Engine"; + layout->addChild(engineButton); + +- LibraryButton* libraryButton = new LibraryButton; +- libraryButton->text = "Library"; +- layout->addChild(libraryButton); +- + HelpButton* helpButton = new HelpButton; + helpButton->text = "Help"; + layout->addChild(helpButton); +@@ -971,7 +680,11 @@ + + + widget::Widget* createMenuBar() { +- menuBar::MenuBar* menuBar = new menuBar::MenuBar; ++ return new widget::Widget; ++} ++ ++widget::Widget* createMenuBar(const bool isStandalone) { ++ menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone); + return menuBar; + } + diff --git a/src/override/diffs/Model.cpp.diff b/src/override/diffs/Model.cpp.diff new file mode 100644 index 0000000..db373a9 --- /dev/null +++ b/src/override/diffs/Model.cpp.diff @@ -0,0 +1,129 @@ +--- ../Rack/src/plugin/Model.cpp 2021-10-17 13:57:23.257633662 +0100 ++++ Model.cpp 2022-01-23 17:13:22.080013846 +0000 +@@ -1,3 +1,30 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's plugin/Model.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. ++ */ ++ + #include + + #include +@@ -17,7 +44,7 @@ + + + void Model::fromJson(json_t* rootJ) { +- assert(plugin); ++ DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr,); + + json_t* nameJ = json_object_get(rootJ, "name"); + if (nameJ) +@@ -54,11 +81,6 @@ + if (manualUrlJ) + manualUrl = json_string_value(manualUrlJ); + +- // modularGridUrl +- json_t* modularGridUrlJ = json_object_get(rootJ, "modularGridUrl"); +- if (modularGridUrlJ) +- modularGridUrl = json_string_value(modularGridUrlJ); +- + // hidden + json_t* hiddenJ = json_object_get(rootJ, "hidden"); + // Use `disabled` as an alias which was deprecated in Rack 2.0 +@@ -73,7 +95,7 @@ + + + std::string Model::getFullName() { +- assert(plugin); ++ DISTRHO_SAFE_ASSERT_RETURN(plugin, {}); + return plugin->getBrand() + " " + name; + } + +@@ -95,7 +117,7 @@ + } + + +-void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { ++void Model::appendContextMenu(ui::Menu* menu, bool) { + // plugin + menu->addChild(createMenuItem("Plugin: " + plugin->name, "", [=]() { + system::openBrowser(plugin->pluginUrl); +@@ -132,18 +154,6 @@ + + menu->addChild(new ui::MenuSeparator); + +- // VCV Library page +- menu->addChild(createMenuItem("VCV Library page", "", [=]() { +- system::openBrowser("https://library.vcvrack.com/" + plugin->slug + "/" + slug); +- })); +- +- // modularGridUrl +- if (modularGridUrl != "") { +- menu->addChild(createMenuItem("ModularGrid page", "", [=]() { +- system::openBrowser(modularGridUrl); +- })); +- } +- + // manual + std::string manualUrl = getManualUrl(); + if (manualUrl != "") { +@@ -172,35 +182,15 @@ + system::openBrowser(plugin->changelogUrl); + })); + } +- +- // plugin folder +- if (plugin->path != "") { +- menu->addChild(createMenuItem("Open plugin folder", "", [=]() { +- system::openDirectory(plugin->path); +- })); +- } +- +- // Favorite +- std::string favoriteRightText = inBrowser ? (RACK_MOD_CTRL_NAME "+click") : ""; +- if (isFavorite()) +- favoriteRightText += " " CHECKMARK_STRING; +- menu->addChild(createMenuItem("Favorite", favoriteRightText, +- [=]() { +- setFavorite(!isFavorite()); +- } +- )); + } + + + bool Model::isFavorite() { +- const settings::ModuleInfo* mi = settings::getModuleInfo(plugin->slug, slug); +- return mi && mi->favorite; ++ return false; + } + + +-void Model::setFavorite(bool favorite) { +- settings::ModuleInfo& mi = settings::moduleInfos[plugin->slug][slug]; +- mi.favorite = favorite; ++void Model::setFavorite(bool) { + } + + diff --git a/src/override/diffs/Scene.cpp.diff b/src/override/diffs/Scene.cpp.diff new file mode 100644 index 0000000..5a29cf7 --- /dev/null +++ b/src/override/diffs/Scene.cpp.diff @@ -0,0 +1,234 @@ +--- ../Rack/src/app/Scene.cpp 2021-12-14 21:35:44.414568198 +0000 ++++ Scene.cpp 2022-01-23 17:13:24.715889665 +0000 +@@ -1,3 +1,30 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's app/Scene.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. ++ */ ++ + #include + + #include +@@ -14,31 +41,49 @@ + #include + #include + ++#include "../CardinalCommon.hpp" ++ + + namespace rack { + namespace app { + + + struct ResizeHandle : widget::OpaqueWidget { +- math::Vec size; +- + void draw(const DrawArgs& args) override { ++ nvgStrokeColor(args.vg, nvgRGBf(1, 1, 1)); ++ nvgStrokeWidth(args.vg, 1); ++ + nvgBeginPath(args.vg); +- nvgMoveTo(args.vg, box.size.x, box.size.y); ++ nvgMoveTo(args.vg, box.size.x, 0); + nvgLineTo(args.vg, 0, box.size.y); +- nvgLineTo(args.vg, box.size.x, 0); +- nvgClosePath(args.vg); +- nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.15)); +- nvgFill(args.vg); +- } ++ nvgStroke(args.vg); + +- void onDragStart(const DragStartEvent& e) override { +- size = APP->window->getSize(); +- } ++ nvgBeginPath(args.vg); ++ nvgMoveTo(args.vg, box.size.x + 5, 0); ++ nvgLineTo(args.vg, 0, box.size.y + 5); ++ nvgStroke(args.vg); ++ ++ nvgBeginPath(args.vg); ++ nvgMoveTo(args.vg, box.size.x + 10, 0); ++ nvgLineTo(args.vg, 0, box.size.y + 10); ++ nvgStroke(args.vg); ++ ++ nvgStrokeColor(args.vg, nvgRGBf(0, 0, 0)); ++ ++ nvgBeginPath(args.vg); ++ nvgMoveTo(args.vg, box.size.x+1, 0); ++ nvgLineTo(args.vg, 0, box.size.y+1); ++ nvgStroke(args.vg); + +- void onDragMove(const DragMoveEvent& e) override { +- size = size.plus(e.mouseDelta); +- APP->window->setSize(size.round()); ++ nvgBeginPath(args.vg); ++ nvgMoveTo(args.vg, box.size.x + 6, 0); ++ nvgLineTo(args.vg, 0, box.size.y + 6); ++ nvgStroke(args.vg); ++ ++ nvgBeginPath(args.vg); ++ nvgMoveTo(args.vg, box.size.x + 11, 0); ++ nvgLineTo(args.vg, 0, box.size.y + 11); ++ nvgStroke(args.vg); + } + }; + +@@ -46,12 +91,15 @@ + struct Scene::Internal { + ResizeHandle* resizeHandle; + +- double lastAutosaveTime = 0.0; +- + bool heldArrowKeys[4] = {}; + }; + + ++void hideResizeHandle(Scene* scene) { ++ scene->internal->resizeHandle->hide(); ++} ++ ++ + Scene::Scene() { + internal = new Internal; + +@@ -67,13 +115,8 @@ + browser->hide(); + addChild(browser); + +- if (settings::showTipsOnLaunch) { +- addChild(tipWindowCreate()); +- } +- + internal->resizeHandle = new ResizeHandle; +- internal->resizeHandle->box.size = math::Vec(15, 15); +- internal->resizeHandle->hide(); ++ internal->resizeHandle->box.size = math::Vec(16, 16); + addChild(internal->resizeHandle); + } + +@@ -89,32 +132,13 @@ + + + void Scene::step() { +- if (APP->window->isFullScreen()) { +- // Expand RackScrollWidget to cover entire screen if fullscreen +- rackScroll->box.pos.y = 0; +- } +- else { +- // Always show MenuBar if not fullscreen +- menuBar->show(); +- rackScroll->box.pos.y = menuBar->box.size.y; +- } +- + internal->resizeHandle->box.pos = box.size.minus(internal->resizeHandle->box.size); + + // Resize owned descendants + menuBar->box.size.x = box.size.x; ++ rackScroll->box.pos.y = menuBar->box.size.y; + rackScroll->box.size = box.size.minus(rackScroll->box.pos); + +- // Autosave periodically +- if (settings::autosaveInterval > 0.0) { +- double time = system::getTime(); +- if (time - internal->lastAutosaveTime >= settings::autosaveInterval) { +- internal->lastAutosaveTime = time; +- APP->patch->saveAutosave(); +- settings::save(); +- } +- } +- + // Scroll RackScrollWidget with arrow keys + math::Vec arrowDelta; + if (internal->heldArrowKeys[0]) { +@@ -172,7 +196,7 @@ + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { + // DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str()); + if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { +- APP->patch->loadTemplateDialog(); ++ patchUtils::loadTemplateDialog(); + e.consume(this); + } + if (e.keyName == "q" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { +@@ -180,19 +204,20 @@ + e.consume(this); + } + if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { +- APP->patch->loadDialog(); ++ patchUtils::loadDialog(); + e.consume(this); + } + if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { +- APP->patch->revertDialog(); ++ patchUtils::revertDialog(); + e.consume(this); + } + if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { +- APP->patch->saveDialog(); ++ // NOTE: will do nothing if path is empty, intentionally ++ patchUtils::saveDialog(APP->patch->path); + e.consume(this); + } + if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { +- APP->patch->saveAsDialog(); ++ patchUtils::saveAsDialog(); + e.consume(this); + } + if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { +@@ -232,10 +257,8 @@ + settings::cpuMeter ^= true; + e.consume(this); + } +- if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) { +- APP->window->setFullScreen(!APP->window->isFullScreen()); +- // The MenuBar will be hidden when the mouse moves over the RackScrollWidget. +- // menuBar->hide(); ++ if (e.key == GLFW_KEY_F7 && (e.mods & RACK_MOD_MASK) == 0) { ++ patchUtils::deployToMOD(); + e.consume(this); + } + +@@ -326,13 +349,6 @@ + + // Key commands that can be overridden by children + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { +- // Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. +- if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { +- if (APP->window->isFullScreen()) { +- APP->window->setFullScreen(false); +- e.consume(this); +- } +- } + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + rack->pasteClipboardAction(); + e.consume(this); +@@ -351,7 +367,7 @@ + std::string extension = system::getExtension(path); + + if (extension == ".vcv") { +- APP->patch->loadPathDialog(path); ++ patchUtils::loadPathDialog(path); + e.consume(this); + return; + } diff --git a/src/override/diffs/Window.cpp.diff b/src/override/diffs/Window.cpp.diff new file mode 100644 index 0000000..58978a6 --- /dev/null +++ b/src/override/diffs/Window.cpp.diff @@ -0,0 +1,1052 @@ +--- ../Rack/src/window/Window.cpp 2022-01-05 19:24:25.995101080 +0000 ++++ Window.cpp 2022-01-23 17:13:27.375765288 +0000 +@@ -1,33 +1,73 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's window/Window.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. ++ */ ++ + #include + #include + #include + +-#if defined ARCH_MAC +- // For CGAssociateMouseAndMouseCursorPosition +- #include +-#endif +- +-#include +-#include +- + #include + #include + #include + #include +-#include +-#include + #include + #include + #include + #include // used in Window::screenshot + #include // used in Window::screenshot + ++#ifdef NDEBUG ++# undef DEBUG ++#endif ++ ++#include "DistrhoUI.hpp" ++#include "Application.hpp" ++#include "../CardinalCommon.hpp" ++#include "../WindowParameters.hpp" ++ ++#ifndef DGL_NO_SHARED_RESOURCES ++# include "src/Resources.hpp" ++#endif + + namespace rack { + namespace window { + + +-static const math::Vec WINDOW_SIZE_MIN = math::Vec(480, 320); ++static const math::Vec WINDOW_SIZE_MIN = math::Vec(648, 538); ++ ++ ++struct FontWithOriginalContext : Font { ++ int ohandle = -1; ++ std::string ofilename; ++}; ++ ++struct ImageWithOriginalContext : Image { ++ int ohandle = -1; ++ std::string ofilename; ++}; + + + Font::~Font() { +@@ -82,369 +122,241 @@ + struct Window::Internal { + std::string lastWindowTitle; + +- int lastWindowX = 0; +- int lastWindowY = 0; +- int lastWindowWidth = 0; +- int lastWindowHeight = 0; ++ DISTRHO_NAMESPACE::UI* ui = nullptr; ++ DISTRHO_NAMESPACE::WindowParameters params; ++ DISTRHO_NAMESPACE::WindowParametersCallback* callback = nullptr; ++ DGL_NAMESPACE::Application hiddenApp; ++ DGL_NAMESPACE::Window hiddenWindow; ++ NVGcontext* r_vg = nullptr; ++ NVGcontext* r_fbVg = nullptr; ++ NVGcontext* o_vg = nullptr; ++ NVGcontext* o_fbVg = nullptr; ++ ++ math::Vec size = WINDOW_SIZE_MIN; ++ ++ int mods = 0; ++ int currentRateLimit = 0; + + int frame = 0; +- bool ignoreNextMouseDelta = false; +- int frameSwapInterval = -1; +- double monitorRefreshRate = 0.0; ++ int frameSwapInterval = 1; ++ double monitorRefreshRate = 60.0; + double frameTime = 0.0; + double lastFrameDuration = 0.0; + +- math::Vec lastMousePos; +- +- std::map> fontCache; +- std::map> imageCache; ++ std::map> fontCache; ++ std::map> imageCache; + + bool fbDirtyOnSubpixelChange = true; + int fbCount = 0; +-}; +- + +-static void windowPosCallback(GLFWwindow* win, int x, int y) { +- if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED)) +- return; +- if (glfwGetWindowAttrib(win, GLFW_ICONIFIED)) +- return; +- if (glfwGetWindowMonitor(win)) +- return; +- settings::windowPos = math::Vec(x, y); +- // DEBUG("windowPosCallback %d %d", x, y); +-} ++ Internal() ++ : hiddenApp(false), ++ hiddenWindow(hiddenApp) { hiddenApp.idle(); } ++}; + + +-static void windowSizeCallback(GLFWwindow* win, int width, int height) { +- if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED)) +- return; +- if (glfwGetWindowAttrib(win, GLFW_ICONIFIED)) +- return; +- if (glfwGetWindowMonitor(win)) +- return; +- settings::windowSize = math::Vec(width, height); +- // DEBUG("windowSizeCallback %d %d", width, height); +-} ++#ifndef DGL_NO_SHARED_RESOURCES ++static int loadFallbackFont(NVGcontext* const vg) ++{ ++ const int font = nvgFindFont(vg, NANOVG_DEJAVU_SANS_TTF); ++ if (font >= 0) ++ return font; + ++ using namespace dpf_resources; + +-static void windowMaximizeCallback(GLFWwindow* win, int maximized) { +- settings::windowMaximized = maximized; +- // DEBUG("windowMaximizeCallback %d", maximized); ++ return nvgCreateFontMem(vg, NANOVG_DEJAVU_SANS_TTF, ++ (uchar*)dejavusans_ttf, dejavusans_ttf_size, 0); + } +- +- +-static void mouseButtonCallback(GLFWwindow* win, int button, int action, int mods) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +-#if defined ARCH_MAC +- // Remap Ctrl-left click to right click on Mac +- 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 Mac +- 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 + +- APP->event->handleButton(APP->window->internal->lastMousePos, button, action, mods); +-} +- +- +-static void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- math::Vec mousePos = math::Vec(xpos, ypos).div(APP->window->pixelRatio / APP->window->windowRatio).round(); +- math::Vec mouseDelta = mousePos.minus(APP->window->internal->lastMousePos); + +- // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked. +- if (APP->window->internal->ignoreNextMouseDelta) { +- APP->window->internal->ignoreNextMouseDelta = false; +- mouseDelta = math::Vec(); +- } ++Window::Window() { ++ internal = new Internal; + +- int cursorMode = glfwGetInputMode(win, GLFW_CURSOR); +- (void) cursorMode; ++ DGL_NAMESPACE::Window::ScopedGraphicsContext sgc(internal->hiddenWindow); + +-#if defined ARCH_MAC +- // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own. +- // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped. +- if (cursorMode == GLFW_CURSOR_HIDDEN) { +- // CGSetLocalEventsSuppressionInterval(0.0); +- glfwSetCursorPos(win, APP->window->internal->lastMousePos.x, APP->window->internal->lastMousePos.y); +- CGAssociateMouseAndMouseCursorPosition(true); +- mousePos = APP->window->internal->lastMousePos; +- } +- // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window +- glfwSetCursor(win, NULL); ++ // Set up NanoVG ++ const int nvgFlags = NVG_ANTIALIAS; ++ vg = nvgCreateGL(nvgFlags); ++ DISTRHO_SAFE_ASSERT_RETURN(vg != nullptr,); ++#ifdef NANOVG_GLES2 ++ fbVg = nvgCreateSharedGLES2(vg, nvgFlags); ++#else ++ fbVg = nvgCreateSharedGL2(vg, nvgFlags); + #endif + +- APP->window->internal->lastMousePos = mousePos; +- +- APP->event->handleHover(mousePos, mouseDelta); +- +- // Keyboard/mouse MIDI driver +- int width, height; +- glfwGetWindowSize(win, &width, &height); +- math::Vec scaledPos(xpos / width, ypos / height); +- keyboard::mouseMove(scaledPos); +-} +- +- +-static void cursorEnterCallback(GLFWwindow* win, int entered) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- if (!entered) { +- APP->event->handleLeave(); +- } +-} +- +- +-static void scrollCallback(GLFWwindow* win, double x, double y) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- math::Vec scrollDelta = math::Vec(x, y); +-#if defined ARCH_MAC +- scrollDelta = scrollDelta.mult(10.0); ++ // Load default Blendish font ++#ifndef DGL_NO_SHARED_RESOURCES ++ uiFont = std::make_shared(); ++ uiFont->vg = vg; ++ uiFont->handle = loadFallbackFont(vg); ++ ++ std::shared_ptr uiFont2; ++ uiFont2 = std::make_shared(); ++ uiFont2->vg = vg; ++ uiFont2->handle = loadFallbackFont(vg); ++ uiFont2->ofilename = asset::system("res/fonts/DejaVuSans.ttf"); ++ internal->fontCache[uiFont2->ofilename] = uiFont2; + #else +- scrollDelta = scrollDelta.mult(50.0); ++ uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf")); + #endif + +- APP->event->handleScroll(APP->window->internal->lastMousePos, scrollDelta); +-} +- +- +-static void charCallback(GLFWwindow* win, unsigned int codepoint) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- if (APP->event->handleText(APP->window->internal->lastMousePos, codepoint)) +- return; ++ if (uiFont != nullptr) ++ bndSetFont(uiFont->handle); + } + ++void WindowSetPluginUI(Window* const window, DISTRHO_NAMESPACE::UI* const ui) ++{ ++ if (ui != nullptr) ++ { ++ const GLubyte* vendor = glGetString(GL_VENDOR); ++ const GLubyte* renderer = glGetString(GL_RENDERER); ++ const GLubyte* version = glGetString(GL_VERSION); ++ INFO("Renderer: %s %s", vendor, renderer); ++ INFO("OpenGL: %s", version); ++ ++ window->internal->ui = ui; ++ window->internal->size = rack::math::Vec(ui->getWidth(), ui->getHeight()); ++ ++ // Set up NanoVG ++ window->internal->r_vg = ui->getContext(); ++#ifdef NANOVG_GLES2 ++ window->internal->r_fbVg = nvgCreateSharedGLES2(window->internal->r_vg, NVG_ANTIALIAS); ++#else ++ window->internal->r_fbVg = nvgCreateSharedGL2(window->internal->r_vg, NVG_ANTIALIAS); ++#endif + +-static void keyCallback(GLFWwindow* win, int key, int scancode, int action, int mods) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- if (APP->event->handleKey(APP->window->internal->lastMousePos, key, scancode, action, mods)) +- return; +- +- // Keyboard/mouse MIDI driver +- if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) { +- keyboard::press(key); +- } +- if (action == GLFW_RELEASE) { +- keyboard::release(key); +- } +-} ++ // swap contexts ++ window->internal->o_vg = window->vg; ++ window->internal->o_fbVg = window->fbVg; ++ window->vg = window->internal->r_vg; ++ window->fbVg = window->internal->r_fbVg; ++ ++ // also for fonts and images ++ window->uiFont->vg = window->vg; ++ window->uiFont->handle = loadFallbackFont(window->vg); ++ for (auto& font : window->internal->fontCache) ++ { ++ font.second->vg = window->vg; ++ font.second->ohandle = font.second->handle; ++ font.second->handle = nvgCreateFont(window->vg, ++ font.second->ofilename.c_str(), font.second->ofilename.c_str()); ++ } ++ for (auto& image : window->internal->imageCache) ++ { ++ image.second->vg = window->vg; ++ image.second->ohandle = image.second->handle; ++ image.second->handle = nvgCreateImage(window->vg, image.second->ofilename.c_str(), ++ NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); ++ } + ++ // Init settings ++ WindowParametersRestore(window); + +-static void dropCallback(GLFWwindow* win, int count, const char** paths) { +- contextSet((Context*) glfwGetWindowUserPointer(win)); +- std::vector pathsVec; +- for (int i = 0; i < count; i++) { +- pathsVec.push_back(paths[i]); ++ widget::Widget::ContextCreateEvent e; ++ APP->scene->onContextCreate(e); + } +- APP->event->handleDrop(APP->window->internal->lastMousePos, pathsVec); +-} +- +- +-static void errorCallback(int error, const char* description) { +- WARN("GLFW error %d: %s", error, description); +-} +- +- +-Window::Window() { +- internal = new Internal; +- int err; ++ else ++ { ++ widget::Widget::ContextDestroyEvent e; ++ APP->scene->onContextDestroy(e); + +- // Set window hints +-#if defined NANOVG_GL2 +- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); +- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); +-#elif defined NANOVG_GL3 +- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); +- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); +- glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +-#endif +- glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); +- glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); ++ // swap contexts ++ window->uiFont->vg = window->internal->o_vg; ++ window->vg = window->internal->o_vg; ++ window->fbVg = window->internal->o_fbVg; ++ window->internal->o_vg = nullptr; ++ window->internal->o_fbVg = nullptr; ++ ++ // also for fonts and images ++ window->uiFont->vg = window->vg; ++ window->uiFont->handle = loadFallbackFont(window->vg); ++ for (auto& font : window->internal->fontCache) ++ { ++ font.second->vg = window->vg; ++ font.second->handle = font.second->ohandle; ++ font.second->ohandle = -1; ++ } ++ for (auto& image : window->internal->imageCache) ++ { ++ image.second->vg = window->vg; ++ image.second->handle = image.second->ohandle; ++ image.second->ohandle = -1; ++ } + +-#if defined ARCH_MAC +- glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); ++#if defined NANOVG_GLES2 ++ nvgDeleteGLES2(window->internal->r_fbVg); ++#else ++ nvgDeleteGL2(window->internal->r_fbVg); + #endif + +- // Create window +- win = glfwCreateWindow(1024, 720, "", NULL, NULL); +- if (!win) { +- osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not open GLFW window. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed."); +- throw Exception("Could not create Window"); +- } +- +- float contentScale; +- glfwGetWindowContentScale(win, &contentScale, NULL); +- INFO("Window content scale: %f", contentScale); +- +- glfwSetWindowSizeLimits(win, WINDOW_SIZE_MIN.x, WINDOW_SIZE_MIN.y, GLFW_DONT_CARE, GLFW_DONT_CARE); +- if (settings::windowSize.x > 0 && settings::windowSize.y > 0) { +- glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y); +- } +- if (settings::windowPos.x > -32000 && settings::windowPos.y > -32000) { +- glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y); +- } +- if (settings::windowMaximized) { +- glfwMaximizeWindow(win); +- } +- glfwShowWindow(win); +- +- glfwSetWindowUserPointer(win, contextGet()); +- glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1); +- +- glfwMakeContextCurrent(win); +- glfwSwapInterval(1); +- const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); +- if (monitorMode->refreshRate > 0) { +- internal->monitorRefreshRate = monitorMode->refreshRate; +- } +- else { +- // Some monitors report 0Hz refresh rate for some reason, so as a workaround, assume 60Hz. +- internal->monitorRefreshRate = 60; +- } +- +- // Set window callbacks +- glfwSetWindowPosCallback(win, windowPosCallback); +- glfwSetWindowSizeCallback(win, windowSizeCallback); +- glfwSetWindowMaximizeCallback(win, windowMaximizeCallback); +- glfwSetMouseButtonCallback(win, mouseButtonCallback); +- // Call this ourselves, but on every frame instead of only when the mouse moves +- // glfwSetCursorPosCallback(win, cursorPosCallback); +- glfwSetCursorEnterCallback(win, cursorEnterCallback); +- glfwSetScrollCallback(win, scrollCallback); +- glfwSetCharCallback(win, charCallback); +- glfwSetKeyCallback(win, keyCallback); +- glfwSetDropCallback(win, dropCallback); +- +- // Set up GLEW +- glewExperimental = GL_TRUE; +- err = glewInit(); +- if (err != GLEW_OK) { +- osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLEW. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed."); +- throw Exception("Could not initialize GLEW"); +- } +- +- const GLubyte* vendor = glGetString(GL_VENDOR); +- const GLubyte* renderer = glGetString(GL_RENDERER); +- const GLubyte* version = glGetString(GL_VERSION); +- INFO("Renderer: %s %s", vendor, renderer); +- INFO("OpenGL: %s", version); +- +- // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here. +- glGetError(); +- +- // Set up NanoVG +- int nvgFlags = NVG_ANTIALIAS; +-#if defined NANOVG_GL2 +- vg = nvgCreateGL2(nvgFlags); +- fbVg = nvgCreateSharedGL2(vg, nvgFlags); +-#elif defined NANOVG_GL3 +- vg = nvgCreateGL3(nvgFlags); +-#elif defined NANOVG_GLES2 +- vg = nvgCreateGLES2(nvgFlags); +-#endif +- if (!vg) { +- osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed."); +- throw Exception("Could not initialize NanoVG"); +- } +- +- // Load default Blendish font +- uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf")); +- bndSetFont(uiFont->handle); +- +- if (APP->scene) { +- widget::Widget::ContextCreateEvent e; +- APP->scene->onContextCreate(e); ++ window->internal->ui = nullptr; ++ window->internal->callback = nullptr; + } + } + ++void WindowSetMods(Window* const window, const int mods) ++{ ++ window->internal->mods = mods; ++} + + Window::~Window() { +- if (APP->scene) { +- widget::Widget::ContextDestroyEvent e; +- APP->scene->onContextDestroy(e); +- } +- +- // Fonts and Images in the cache must be deleted before the NanoVG context is deleted +- internal->fontCache.clear(); +- internal->imageCache.clear(); +- +- // nvgDeleteClone(fbVg); +- +-#if defined NANOVG_GL2 +- nvgDeleteGL2(vg); +- nvgDeleteGL2(fbVg); +-#elif defined NANOVG_GL3 +- nvgDeleteGL3(vg); +-#elif defined NANOVG_GLES2 +- nvgDeleteGLES2(vg); ++ { ++ DGL_NAMESPACE::Window::ScopedGraphicsContext sgc(internal->hiddenWindow); ++ internal->hiddenWindow.close(); ++ internal->hiddenApp.idle(); ++ ++ // Fonts and Images in the cache must be deleted before the NanoVG context is deleted ++ internal->fontCache.clear(); ++ internal->imageCache.clear(); ++ ++#if defined NANOVG_GLES2 ++ nvgDeleteGLES2(internal->o_fbVg != nullptr ? internal->o_fbVg : fbVg); ++ nvgDeleteGLES2(internal->o_vg != nullptr ? internal->o_vg : vg); ++#else ++ nvgDeleteGL2(internal->o_fbVg != nullptr ? internal->o_fbVg : fbVg); ++ nvgDeleteGL2(internal->o_vg != nullptr ? internal->o_vg : vg); + #endif ++ } + +- glfwDestroyWindow(win); + delete internal; + } + + + math::Vec Window::getSize() { +- int width, height; +- glfwGetWindowSize(win, &width, &height); +- return math::Vec(width, height); ++ return internal->size; + } + + + void Window::setSize(math::Vec size) { + size = size.max(WINDOW_SIZE_MIN); +- glfwSetWindowSize(win, size.x, size.y); ++ internal->size = size; ++ ++ if (DISTRHO_NAMESPACE::UI* const ui = internal->ui) ++ ui->setSize(internal->size.x, internal->size.y); + } + + + void Window::run() { + internal->frame = 0; +- while (!glfwWindowShouldClose(win)) { +- step(); +- } + } + + + void Window::step() { ++ DISTRHO_SAFE_ASSERT_RETURN(internal->ui != nullptr,); ++ + double frameTime = system::getTime(); + double lastFrameTime = internal->frameTime; + internal->frameTime = frameTime; + internal->lastFrameDuration = frameTime - lastFrameTime; + internal->fbCount = 0; + // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration); +- // double t1 = 0.0, t2 = 0.0, t3 = 0.0, t4 = 0.0, t5 = 0.0; + + // Make event handlers and step() have a clean NanoVG context + nvgReset(vg); + +- bndSetFont(uiFont->handle); +- +- // Poll events +- // Save and restore context because event handler set their own context based on which window they originate from. +- Context* context = contextGet(); +- glfwPollEvents(); +- contextSet(context); +- +- // In case glfwPollEvents() sets another OpenGL context +- glfwMakeContextCurrent(win); +- if (settings::frameSwapInterval != internal->frameSwapInterval) { +- glfwSwapInterval(settings::frameSwapInterval); +- internal->frameSwapInterval = settings::frameSwapInterval; +- } +- +- // Call cursorPosCallback every frame, not just when the mouse moves +- { +- double xpos, ypos; +- glfwGetCursorPos(win, &xpos, &ypos); +- cursorPosCallback(win, xpos, ypos); +- } +- gamepad::step(); ++ if (uiFont != nullptr) ++ bndSetFont(uiFont->handle); + + // Set window title + std::string windowTitle = APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION; +@@ -455,31 +367,23 @@ + windowTitle += system::getFilename(APP->patch->path); + } + if (windowTitle != internal->lastWindowTitle) { +- glfwSetWindowTitle(win, windowTitle.c_str()); ++ internal->ui->getWindow().setTitle(windowTitle.c_str()); + internal->lastWindowTitle = windowTitle; + } + + // Get desired pixel ratio +- float newPixelRatio; +- if (settings::pixelRatio > 0.0) { +- newPixelRatio = settings::pixelRatio; +- } +- else { +- glfwGetWindowContentScale(win, &newPixelRatio, NULL); +- newPixelRatio = std::floor(newPixelRatio + 0.5); +- } ++ float newPixelRatio = internal->ui->getScaleFactor(); + if (newPixelRatio != pixelRatio) { + pixelRatio = newPixelRatio; + APP->event->handleDirty(); + } + + // Get framebuffer/window ratio +- int fbWidth, fbHeight; +- glfwGetFramebufferSize(win, &fbWidth, &fbHeight); +- int winWidth, winHeight; +- glfwGetWindowSize(win, &winWidth, &winHeight); ++ int winWidth = internal->ui->getWidth(); ++ int winHeight = internal->ui->getHeight(); ++ int fbWidth = winWidth;// * newPixelRatio; ++ int fbHeight = winHeight;// * newPixelRatio; + windowRatio = (float)fbWidth / winWidth; +- // t1 = system::getTime(); + + if (APP->scene) { + // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth); +@@ -488,13 +392,10 @@ + + // Step scene + APP->scene->step(); +- // t2 = system::getTime(); + + // Render scene +- bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED); +- if (visible) { ++ { + // Update and render +- nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio); + nvgScale(vg, pixelRatio, pixelRatio); + + // Draw scene +@@ -502,196 +403,60 @@ + args.vg = vg; + args.clipBox = APP->scene->box.zeroPos(); + APP->scene->draw(args); +- // t3 = system::getTime(); + + glViewport(0, 0, fbWidth, fbHeight); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +- nvgEndFrame(vg); +- // t4 = system::getTime(); + } + } + +- glfwSwapBuffers(win); +- +- // On some platforms, glfwSwapBuffers() doesn't wait on monitor refresh, so we have to sleep as a fallback. +- double frameDurationRemaining = getFrameDurationRemaining(); +- if (frameDurationRemaining > 0.0) { +- std::this_thread::sleep_for(std::chrono::duration(frameDurationRemaining)); +- } +- +- // t5 = system::getTime(); +- +- // DEBUG("pre-step %6.1f step %6.1f draw %6.1f nvgEndFrame %6.1f glfwSwapBuffers %6.1f total %6.1f", +- // (t1 - frameTime) * 1e3f, +- // (t2 - t1) * 1e3f, +- // (t3 - t2) * 1e3f, +- // (t4 - t2) * 1e3f, +- // (t5 - t4) * 1e3f, +- // (t5 - frameTime) * 1e3f +- // ); +- internal->frame++; ++ ++internal->frame; + } + + + void Window::activateContext() { +- glfwMakeContextCurrent(win); + } + + +-static void flipBitmap(uint8_t* pixels, int width, int height, int depth) { +- for (int y = 0; y < height / 2; y++) { +- int flipY = height - y - 1; +- uint8_t tmp[width * depth]; +- std::memcpy(tmp, &pixels[y * width * depth], width * depth); +- std::memcpy(&pixels[y * width * depth], &pixels[flipY * width * depth], width * depth); +- std::memcpy(&pixels[flipY * width * depth], tmp, width * depth); +- } ++void Window::screenshot(const std::string&) { + } + + +-void Window::screenshot(const std::string& screenshotPath) { +- // Get window framebuffer size +- int width, height; +- glfwGetFramebufferSize(APP->window->win, &width, &height); +- +- // Allocate pixel color buffer +- uint8_t* pixels = new uint8_t[height * width * 4]; +- +- // glReadPixels defaults to GL_BACK, but the back-buffer is unstable, so use the front buffer (what the user sees) +- glReadBuffer(GL_FRONT); +- glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +- +- // Write pixels to PNG +- flipBitmap(pixels, width, height, 4); +- stbi_write_png(screenshotPath.c_str(), width, height, 4, pixels, width * 4); +- +- delete[] pixels; +-} +- +- +-void Window::screenshotModules(const std::string& screenshotsDir, float zoom) { +- // Iterate plugins and create directories +- system::createDirectories(screenshotsDir); +- for (plugin::Plugin* p : plugin::plugins) { +- std::string dir = system::join(screenshotsDir, p->slug); +- system::createDirectory(dir); +- for (plugin::Model* model : p->models) { +- std::string filename = system::join(dir, model->slug + ".png"); +- +- // Skip model if screenshot already exists +- if (system::isFile(filename)) +- continue; +- +- INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str()); +- +- // Create widgets +- widget::FramebufferWidget* fbw = new widget::FramebufferWidget; +- fbw->oversample = 2; +- +- struct ModuleWidgetContainer : widget::Widget { +- void draw(const DrawArgs& args) override { +- Widget::draw(args); +- Widget::drawLayer(args, 1); +- } +- }; +- ModuleWidgetContainer* mwc = new ModuleWidgetContainer; +- fbw->addChild(mwc); +- +- app::ModuleWidget* mw = model->createModuleWidget(NULL); +- mwc->box.size = mw->box.size; +- fbw->box.size = mw->box.size; +- mwc->addChild(mw); +- +- // Step to allow the ModuleWidget state to set its default appearance. +- fbw->step(); +- +- // Draw to framebuffer +- fbw->render(math::Vec(zoom, zoom)); +- +- // Read pixels +- nvgluBindFramebuffer(fbw->getFramebuffer()); +- int width, height; +- nvgImageSize(vg, fbw->getImageHandle(), &width, &height); +- uint8_t* pixels = new uint8_t[height * width * 4]; +- glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +- +- // Write pixels to PNG +- flipBitmap(pixels, width, height, 4); +- stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4); +- +- // Cleanup +- delete[] pixels; +- nvgluBindFramebuffer(NULL); +- delete fbw; +- } +- } ++void Window::screenshotModules(const std::string&, float) { + } + + + void Window::close() { +- glfwSetWindowShouldClose(win, GLFW_TRUE); ++ DISTRHO_SAFE_ASSERT_RETURN(internal->ui != nullptr,); ++ ++ internal->ui->getWindow().close(); + } + + + void Window::cursorLock() { +- if (!settings::allowCursorLock) +- return; +- +-#if defined ARCH_MAC +- glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +-#else +- glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +-#endif +- internal->ignoreNextMouseDelta = true; + } + + + void Window::cursorUnlock() { +- if (!settings::allowCursorLock) +- return; +- +- glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +- internal->ignoreNextMouseDelta = true; + } + + + bool Window::isCursorLocked() { +- return glfwGetInputMode(win, GLFW_CURSOR) != GLFW_CURSOR_NORMAL; ++ return false; + } + + + int Window::getMods() { +- int mods = 0; +- 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; ++ return internal->mods; + } + + +-void Window::setFullScreen(bool fullScreen) { +- if (!fullScreen) { +- glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE); +- } +- else { +- glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY); +- glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight); +- GLFWmonitor* monitor = glfwGetPrimaryMonitor(); +- const GLFWvidmode* mode = glfwGetVideoMode(monitor); +- glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); +- } ++void Window::setFullScreen(bool) { + } + + + bool Window::isFullScreen() { +- GLFWmonitor* monitor = glfwGetWindowMonitor(win); +- return monitor != NULL; ++ return false; + } + + +@@ -722,14 +487,15 @@ + return pair->second; + + // Load font +- std::shared_ptr font; ++ std::shared_ptr font; + try { +- font = std::make_shared(); ++ font = std::make_shared(); ++ font->ofilename = filename; + font->loadFile(filename, vg); + } + catch (Exception& e) { + WARN("%s", e.what()); +- font = NULL; ++ font = nullptr; + } + internal->fontCache[filename] = font; + return font; +@@ -742,14 +508,15 @@ + return pair->second; + + // Load image +- std::shared_ptr image; ++ std::shared_ptr image; + try { +- image = std::make_shared(); ++ image = std::make_shared(); ++ image->ofilename = filename; + image->loadFile(filename, vg); + } + catch (Exception& e) { + WARN("%s", e.what()); +- image = NULL; ++ image = nullptr; + } + internal->imageCache[filename] = image; + return image; +@@ -767,27 +534,116 @@ + + + void init() { +- int err; +- +- // Set up GLFW +-#if defined ARCH_MAC +- glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE); +- glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_FALSE); +-#endif +- +- glfwSetErrorCallback(errorCallback); +- err = glfwInit(); +- if (err != GLFW_TRUE) { +- osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW."); +- throw Exception("Could not initialize GLFW"); +- } + } + + + void destroy() { +- glfwTerminate(); + } + + + } // namespace window + } // namespace rack ++ ++ ++START_NAMESPACE_DISTRHO ++ ++void WindowParametersSave(rack::window::Window* const window) ++{ ++ if (d_isNotEqual(window->internal->params.cableOpacity, rack::settings::cableOpacity)) ++ { ++ window->internal->params.cableOpacity = rack::settings::cableOpacity; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterCableOpacity, ++ rack::settings::cableOpacity); ++ } ++ if (d_isNotEqual(window->internal->params.cableTension, rack::settings::cableTension)) ++ { ++ window->internal->params.cableTension = rack::settings::cableTension; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterCableTension, ++ rack::settings::cableTension); ++ } ++ if (d_isNotEqual(window->internal->params.rackBrightness, rack::settings::rackBrightness)) ++ { ++ window->internal->params.rackBrightness = rack::settings::rackBrightness; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterRackBrightness, ++ rack::settings::rackBrightness); ++ } ++ if (d_isNotEqual(window->internal->params.haloBrightness, rack::settings::haloBrightness)) ++ { ++ window->internal->params.haloBrightness = rack::settings::haloBrightness; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterHaloBrightness, ++ rack::settings::haloBrightness); ++ } ++ if (d_isNotEqual(window->internal->params.knobScrollSensitivity, rack::settings::knobScrollSensitivity)) ++ { ++ window->internal->params.knobScrollSensitivity = rack::settings::knobScrollSensitivity; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterWheelSensitivity, ++ rack::settings::knobScrollSensitivity); ++ } ++ if (window->internal->params.knobMode != rack::settings::knobMode) ++ { ++ window->internal->params.knobMode = rack::settings::knobMode; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterKnobMode, ++ rack::settings::knobMode); ++ } ++ if (window->internal->params.tooltips != rack::settings::tooltips) ++ { ++ window->internal->params.tooltips = rack::settings::tooltips; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterShowTooltips, ++ rack::settings::tooltips); ++ } ++ if (window->internal->params.knobScroll != rack::settings::knobScroll) ++ { ++ window->internal->params.knobScroll = rack::settings::knobScroll; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterWheelKnobControl, ++ rack::settings::knobScroll); ++ } ++ if (window->internal->params.lockModules != rack::settings::lockModules) ++ { ++ window->internal->params.lockModules = rack::settings::lockModules; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterLockModulePositions, ++ rack::settings::lockModules); ++ } ++ if (window->internal->params.rateLimit != rack::settings::rateLimit) ++ { ++ window->internal->params.rateLimit = rack::settings::rateLimit; ++ if (window->internal->callback != nullptr) ++ window->internal->callback->WindowParametersChanged(kWindowParameterUpdateRateLimit, ++ rack::settings::rateLimit); ++ } ++} ++ ++void WindowParametersRestore(rack::window::Window* const window) ++{ ++ rack::settings::cableOpacity = window->internal->params.cableOpacity; ++ rack::settings::cableTension = window->internal->params.cableTension; ++ rack::settings::rackBrightness = window->internal->params.rackBrightness; ++ rack::settings::haloBrightness = window->internal->params.haloBrightness; ++ rack::settings::knobScrollSensitivity = window->internal->params.knobScrollSensitivity; ++ rack::settings::knobMode = static_cast(window->internal->params.knobMode); ++ rack::settings::tooltips = window->internal->params.tooltips; ++ rack::settings::knobScroll = window->internal->params.knobScroll; ++ rack::settings::lockModules = window->internal->params.lockModules; ++ rack::settings::rateLimit = window->internal->params.rateLimit; ++} ++ ++void WindowParametersSetCallback(rack::window::Window* const window, WindowParametersCallback* const callback) ++{ ++ window->internal->callback = callback; ++} ++ ++void WindowParametersSetValues(rack::window::Window* const window, const WindowParameters& params) ++{ ++ window->internal->params = params; ++} ++ ++END_NAMESPACE_DISTRHO ++ diff --git a/src/override/diffs/blendish.c.diff b/src/override/diffs/blendish.c.diff new file mode 100644 index 0000000..79696cd --- /dev/null +++ b/src/override/diffs/blendish.c.diff @@ -0,0 +1,29 @@ +--- ../Rack/dep/oui-blendish/blendish.c 2021-10-17 13:57:24.613620711 +0100 ++++ blendish.c 2021-12-13 09:36:22.182673256 +0000 +@@ -61,7 +61,7 @@ + } + + #else +- #define BND_INLINE inline ++ #define BND_INLINE static inline + #define bnd_fminf(a, b) fminf(a, b) + #define bnd_fmaxf(a, b) fmaxf(a, b) + #define bnd_fmin(a, b) fmin(a, b) +@@ -1061,7 +1061,7 @@ + // search horizontal position + static NVGglyphPosition glyphs[BND_MAX_GLYPHS]; + int nglyphs = nvgTextGlyphPositions( +- ctx, x, y, rows[row].start, rows[row].end + 1, glyphs, BND_MAX_GLYPHS); ++ ctx, x, y, rows[row].start, rows[row].end, glyphs, BND_MAX_GLYPHS); + int col, p = 0; + for (col = 0; col < nglyphs && glyphs[col].x < px; ++col) + p = glyphs[col].str - label; +@@ -1083,7 +1083,7 @@ + if (nrows == 0) return; + *cx = rows[r].minx; + nglyphs = nvgTextGlyphPositions( +- ctx, x, y, rows[r].start, rows[r].end+1, glyphs, BND_MAX_GLYPHS); ++ ctx, x, y, rows[r].start, rows[r].end, glyphs, BND_MAX_GLYPHS); + for (int i=0; i < nglyphs; ++i) { + *cx=glyphs[i].x; + if (glyphs[i].str == caret) break; diff --git a/src/override/diffs/common.cpp.diff b/src/override/diffs/common.cpp.diff new file mode 100644 index 0000000..1437e5c --- /dev/null +++ b/src/override/diffs/common.cpp.diff @@ -0,0 +1,69 @@ +--- ../Rack/src/common.cpp 2021-11-23 19:57:23.719015894 +0000 ++++ common.cpp 2022-01-23 17:13:08.824652617 +0000 +@@ -1,6 +1,38 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's common.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. ++ */ ++ + #include + #include + ++#ifdef NDEBUG ++# undef DEBUG ++#endif ++ ++#include "DistrhoPluginUtils.hpp" + + #if defined ARCH_WIN + #include +@@ -14,20 +46,21 @@ + + namespace rack { + +- +-const std::string APP_NAME = "VCV Rack"; +-const std::string APP_EDITION = "Free"; +-const std::string APP_EDITION_NAME = "Free"; ++const std::string APP_NAME = "Cardinal"; ++const std::string APP_EDITION = getPluginFormatName(); ++const std::string APP_EDITION_NAME = "Audio Plugin"; + const std::string APP_VERSION_MAJOR = "2"; +-const std::string APP_VERSION = TOSTRING(_APP_VERSION); ++const std::string APP_VERSION = "2.0"; + #if defined ARCH_WIN + const std::string APP_OS = "win"; + #elif ARCH_MAC + const std::string APP_OS = "mac"; + #elif defined ARCH_LIN + const std::string APP_OS = "lin"; ++#else ++ #error ARCH_LIN undefined + #endif +-const std::string API_URL = "https://api.vcvrack.com"; ++const std::string API_URL = ""; + + + Exception::Exception(const char* format, ...) { diff --git a/src/override/diffs/context.cpp.diff b/src/override/diffs/context.cpp.diff new file mode 100644 index 0000000..a499c66 --- /dev/null +++ b/src/override/diffs/context.cpp.diff @@ -0,0 +1,57 @@ +--- ../Rack/src/context.cpp 2022-01-15 14:44:46.391280963 +0000 ++++ context.cpp 2022-01-23 17:13:11.652514338 +0000 +@@ -1,3 +1,30 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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 an edited version of VCVRack's context.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. ++ */ ++ + #include + #include + #include +@@ -6,9 +33,13 @@ + #include + #include + ++#ifdef NDEBUG ++# undef DEBUG ++#endif + +-namespace rack { ++#include "DistrhoUtils.hpp" + ++namespace rack { + + Context::~Context() { + // Deleting NULL is safe in C++. +@@ -44,7 +75,7 @@ + static thread_local Context* threadContext = NULL; + + Context* contextGet() { +- assert(threadContext); ++ DISTRHO_SAFE_ASSERT(threadContext != nullptr); + return threadContext; + } +