| @@ -1 +1 @@ | |||
| Subproject commit ae5e26516596024a0268b5f8f1685050a248875a | |||
| Subproject commit 18d8835bd17da6f651a49d83f8d911d7c1858399 | |||
| @@ -0,0 +1,29 @@ | |||
| /* | |||
| * DISTRHO Cardinal Plugin | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 3 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the LICENSE file. | |||
| */ | |||
| #pragma once | |||
| #include_next "settings.hpp" | |||
| namespace rack { | |||
| namespace settings { | |||
| extern bool darkMode; | |||
| extern int rateLimit; | |||
| } // namespace settings | |||
| } // namespace rack | |||
| @@ -16,6 +16,7 @@ | |||
| */ | |||
| #include "plugincontext.hpp" | |||
| #include "ModuleWidgets.hpp" | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -170,7 +171,7 @@ struct HostTime : TerminalModule { | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #ifndef HEADLESS | |||
| struct HostTimeWidget : ModuleWidget { | |||
| struct HostTimeWidget : ModuleWidgetWith8HP { | |||
| static constexpr const float startX = 10.0f; | |||
| static constexpr const float startY_top = 71.0f; | |||
| static constexpr const float startY_cv = 115.0f; | |||
| @@ -186,10 +187,7 @@ struct HostTimeWidget : ModuleWidget { | |||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HostTime.svg"))); | |||
| monoFontPath = asset::system("res/fonts/ShareTechMono-Regular.ttf"); | |||
| addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| createAndAddScrews(); | |||
| addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 0 * padding), m, HostTime::kHostTimeRolling)); | |||
| addOutput(createOutput<PJ301MPort>(Vec(startX, startY_cv + 1 * padding), m, HostTime::kHostTimeReset)); | |||
| @@ -214,20 +212,16 @@ struct HostTimeWidget : ModuleWidget { | |||
| const float y = startY_cv + offset * padding; | |||
| nvgBeginPath(vg); | |||
| nvgRoundedRect(vg, startX - 1.0f, y - 2.f, box.size.x - startX * 2 + 2.f, 28.f, 4); | |||
| nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0)); | |||
| nvgFillColor(vg, rack::settings::darkMode ? nvgRGB(0xd0, 0xd0, 0xd0) : nvgRGB(0x2f, 0x2f, 0x2f)); | |||
| nvgFill(vg); | |||
| nvgBeginPath(vg); | |||
| nvgFillColor(vg, color::BLACK); | |||
| nvgFillColor(vg, rack::settings::darkMode ? color::BLACK : color::WHITE); | |||
| nvgText(vg, startX + 36, y + 16, text, nullptr); | |||
| } | |||
| void draw(const DrawArgs& args) override | |||
| { | |||
| nvgBeginPath(args.vg); | |||
| nvgRect(args.vg, 0, 0, box.size.x, box.size.y); | |||
| nvgFillPaint(args.vg, nvgLinearGradient(args.vg, 0, 0, 0, box.size.y, | |||
| nvgRGB(0x18, 0x19, 0x19), nvgRGB(0x21, 0x22, 0x22))); | |||
| nvgFill(args.vg); | |||
| drawBackground(args.vg); | |||
| nvgFontFaceId(args.vg, 0); | |||
| nvgFontSize(args.vg, 14); | |||
| @@ -17,7 +17,9 @@ | |||
| #pragma once | |||
| #include "color.hpp" | |||
| #include "rack.hpp" | |||
| #include "settings.hpp" | |||
| #ifdef NDEBUG | |||
| # undef DEBUG | |||
| @@ -66,22 +68,26 @@ struct ModuleWidgetWithSideScrews : ModuleWidget { | |||
| void drawBackground(NVGcontext* const vg) { | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, 0, 0, box.size.x, box.size.y); | |||
| nvgFillPaint(vg, nvgLinearGradient(vg, 0, 0, 0, box.size.y, | |||
| nvgRGB(0x18, 0x19, 0x19), nvgRGB(0x21, 0x22, 0x22))); | |||
| if (rack::settings::darkMode) | |||
| nvgFillPaint(vg, nvgLinearGradient(vg, 0, 0, 0, box.size.y, | |||
| nvgRGB(0x18, 0x19, 0x19), nvgRGB(0x21, 0x22, 0x22))); | |||
| else | |||
| nvgFillPaint(vg, nvgLinearGradient(vg, 0, 0, 0, box.size.y, | |||
| nvgRGB(0xe7, 0xe6, 0xe6), nvgRGB(0xde, 0xdd, 0xdd))); | |||
| nvgFill(vg); | |||
| } | |||
| void drawOutputJacksArea(NVGcontext* const vg, const int numOutputs) { | |||
| nvgBeginPath(vg); | |||
| nvgRoundedRect(vg, startX_Out - 2.5f, startY - 2.0f, padding, padding * numOutputs, 4); | |||
| nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0)); | |||
| nvgFillColor(vg, rack::settings::darkMode ? nvgRGB(0xd0, 0xd0, 0xd0) : nvgRGB(0x2f, 0x2f, 0x2f)); | |||
| nvgFill(vg); | |||
| } | |||
| void drawTextLine(NVGcontext* const vg, const uint posY, const char* const text) { | |||
| const float y = startY + posY * padding; | |||
| nvgBeginPath(vg); | |||
| nvgFillColor(vg, color::WHITE); | |||
| nvgFillColor(vg, rack::settings::darkMode ? color::WHITE : color::BLACK); | |||
| nvgText(vg, box.size.x * 0.5f, y + 16, text, nullptr); | |||
| } | |||
| @@ -96,3 +102,4 @@ typedef ModuleWidgetWithSideScrews<3> ModuleWidgetWith3HP; | |||
| typedef ModuleWidgetWithSideScrews<8> ModuleWidgetWith8HP; | |||
| typedef ModuleWidgetWithSideScrews<9> ModuleWidgetWith9HP; | |||
| typedef ModuleWidgetWithSideScrews<11> ModuleWidgetWith11HP; | |||
| typedef ModuleWidgetWithSideScrews<25> ModuleWidgetWith25HP; | |||
| @@ -17,6 +17,7 @@ | |||
| #ifndef HEADLESS | |||
| # include "glBars.hpp" | |||
| # include "ModuleWidgets.hpp" | |||
| # include "Widgets.hpp" | |||
| #else | |||
| # include "plugin.hpp" | |||
| @@ -129,16 +130,13 @@ struct glBarsRendererWidget : OpenGlWidgetWithBrowserPreview { | |||
| } | |||
| }; | |||
| struct glBarsWidget : ModuleWidget { | |||
| struct glBarsWidget : ModuleWidgetWith25HP { | |||
| glBarsWidget(glBarsModule* const module) | |||
| { | |||
| setModule(module); | |||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/glBars.svg"))); | |||
| addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| createAndAddScrews(); | |||
| addInput(createInput<PJ301MPort>(Vec(135.0f, 20.0f), module, glBarsModule::IN1_INPUT)); | |||
| @@ -151,13 +149,8 @@ struct glBarsWidget : ModuleWidget { | |||
| void draw(const DrawArgs& args) override | |||
| { | |||
| nvgBeginPath(args.vg); | |||
| nvgRect(args.vg, 0, 0, box.size.x, box.size.y); | |||
| nvgFillPaint(args.vg, nvgLinearGradient(args.vg, 0, 0, 0, box.size.y, | |||
| nvgRGB(0x18, 0x19, 0x19), nvgRGB(0x21, 0x22, 0x22))); | |||
| nvgFill(args.vg); | |||
| ModuleWidget::draw(args); | |||
| drawBackground(args.vg); | |||
| ModuleWidgetWith25HP::draw(args); | |||
| } | |||
| }; | |||
| #else | |||
| @@ -17,12 +17,12 @@ void writeThemeAndContrastAsDefault() {} | |||
| void saveThemeAndContrastAsDefault(int, float) {} | |||
| void loadThemeAndContrastFromDefault(int* panelTheme, float* panelContrast) { | |||
| *panelTheme = 1; | |||
| *panelTheme = rack::settings::darkMode ? 1 : 0; | |||
| *panelContrast = panelContrastDefault; | |||
| } | |||
| bool isDark(int*) { | |||
| return true; | |||
| return rack::settings::darkMode; | |||
| } | |||
| void readThemeAndContrastFromDefault() {} | |||
| @@ -47,7 +47,7 @@ void PanelBaseWidget::draw(const DrawArgs& args) { | |||
| void InverterWidget::draw(const DrawArgs& args) { | |||
| TransparentWidget::draw(args); | |||
| { | |||
| if (rack::settings::darkMode) { | |||
| // nvgSave(args.vg); | |||
| nvgBeginPath(args.vg); | |||
| nvgFillColor(args.vg, SCHEME_WHITE);// this is the source, the current framebuffer is the dest | |||
| @@ -64,4 +64,3 @@ void InverterWidget::draw(const DrawArgs& args) { | |||
| // nvgRestore(args.vg); | |||
| } | |||
| } | |||
| @@ -30,6 +30,7 @@ | |||
| #include "AsyncDialog.hpp" | |||
| #include "PluginContext.hpp" | |||
| #include "DistrhoPluginUtils.hpp" | |||
| #include "settings.hpp" | |||
| #include <asset.hpp> | |||
| #include <context.hpp> | |||
| @@ -61,6 +62,7 @@ const std::string CARDINAL_VERSION = "22.07"; | |||
| namespace rack { | |||
| namespace settings { | |||
| bool darkMode = true; | |||
| int rateLimit = 0; | |||
| } | |||
| @@ -33,10 +33,6 @@ extern const std::string CARDINAL_VERSION; | |||
| namespace rack { | |||
| namespace settings { | |||
| extern int rateLimit; | |||
| } | |||
| namespace ui { | |||
| struct Menu; | |||
| } | |||
| @@ -114,6 +114,7 @@ BUILD_CXX_FLAGS += -faligned-new -Wno-abi | |||
| endif | |||
| # use our custom function to invert some colors | |||
| BUILD_CXX_FLAGS += -DnsvgDelete=nsvgDeleteCardinal | |||
| BUILD_CXX_FLAGS += -DnsvgParseFromFile=nsvgParseFromFileCardinal | |||
| # Rack code is not tested for this flag, unset it | |||
| @@ -19,6 +19,13 @@ | |||
| #include <cstdio> | |||
| #include <cstring> | |||
| #include <list> | |||
| namespace rack { | |||
| namespace settings { | |||
| extern bool darkMode; | |||
| } | |||
| } | |||
| #include "nanovg.h" | |||
| @@ -43,6 +50,7 @@ NVGcolor nvgRGBblank(unsigned char, unsigned char, unsigned char) | |||
| // Compile those nice implementation-in-header little libraries | |||
| #define NANOSVG_IMPLEMENTATION | |||
| #define NANOSVG_ALL_COLOR_KEYWORDS | |||
| #undef nsvgDelete | |||
| #undef nsvgParseFromFile | |||
| #include <nanosvg.h> | |||
| @@ -327,6 +335,7 @@ static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const c | |||
| // Special case for DrumKit background gradient | |||
| if (std::strncmp(svgFileToInvert, "/DrumKit/", 9) == 0) | |||
| { | |||
| std::free(paint.gradient); | |||
| paint.type = NSVG_PAINT_COLOR; | |||
| paint.color = 0xff191919; | |||
| return true; | |||
| @@ -593,12 +602,33 @@ static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const c | |||
| extern "C" { | |||
| NSVGimage* nsvgParseFromFileCardinal(const char* filename, const char* units, float dpi); | |||
| void nsvgDeleteCardinal(NSVGimage*); | |||
| } | |||
| struct ExtendedNSVGimage { | |||
| NSVGimage* handle; | |||
| NSVGshape* shapesOrig; | |||
| NSVGshape* shapesDark; | |||
| }; | |||
| static std::list<ExtendedNSVGimage> loadedSVGs; | |||
| static void nsvg__duplicatePaint(NSVGpaint& dst, NSVGpaint& src) | |||
| { | |||
| if (dst.type == NSVG_PAINT_LINEAR_GRADIENT || dst.type == NSVG_PAINT_RADIAL_GRADIENT) | |||
| { | |||
| dst.gradient = static_cast<NSVGgradient*>(malloc(sizeof(NSVGgradient))); | |||
| std::memcpy(dst.gradient, src.gradient, sizeof(NSVGgradient)); | |||
| } | |||
| } | |||
| NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* const units, const float dpi) | |||
| { | |||
| if (NSVGimage* const handle = nsvgParseFromFile(filename, units, dpi)) | |||
| { | |||
| bool hasDarkMode = false; | |||
| NSVGshape* shapesOrig; | |||
| NSVGshape* shapesDark; | |||
| for (size_t i = 0; i < sizeof(svgFilesToInvert)/sizeof(svgFilesToInvert[0]); ++i) | |||
| { | |||
| const char* const svgFileToInvert = svgFilesToInvert[i].filename; | |||
| @@ -614,7 +644,30 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con | |||
| const int shapeNumberToIgnore = svgFilesToInvert[i].shapeNumberToIgnore; | |||
| int shapeCounter = 0; | |||
| for (NSVGshape* shape = handle->shapes; shape != nullptr; shape = shape->next, ++shapeCounter) | |||
| hasDarkMode = true; | |||
| shapesOrig = handle->shapes; | |||
| // duplicate all shapes, so we can swap between original and dark mode at will | |||
| shapesDark = static_cast<NSVGshape*>(malloc(sizeof(NSVGshape))); | |||
| std::memcpy(shapesDark, shapesOrig, sizeof(NSVGshape)); | |||
| nsvg__duplicatePaint(shapesDark->fill, shapesOrig->fill); | |||
| nsvg__duplicatePaint(shapesDark->stroke, shapesOrig->stroke); | |||
| for (NSVGshape* shape2 = shapesDark;;) | |||
| { | |||
| if (shape2->next == nullptr) | |||
| break; | |||
| NSVGshape* const shapedup = static_cast<NSVGshape*>(malloc(sizeof(NSVGshape))); | |||
| std::memcpy(shapedup, shape2->next, sizeof(NSVGshape)); | |||
| nsvg__duplicatePaint(shapedup->fill, shape2->next->fill); | |||
| nsvg__duplicatePaint(shapedup->stroke, shape2->next->stroke); | |||
| shape2->next = shapedup; | |||
| shape2 = shapedup; | |||
| } | |||
| // shape paint inversion | |||
| for (NSVGshape* shape = shapesDark; shape != nullptr; shape = shape->next, ++shapeCounter) | |||
| { | |||
| if (shapeNumberToIgnore == shapeCounter) | |||
| continue; | |||
| @@ -635,7 +688,7 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con | |||
| invertPaint(shape, shape->stroke, svgFileToInvert); | |||
| } | |||
| return handle; | |||
| break; | |||
| } | |||
| // Special case for AmalgamatedHarmonics background color | |||
| @@ -643,8 +696,62 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con | |||
| if (std::strstr(filename, "/AmalgamatedHarmonics/") != nullptr) | |||
| handle->shapes->fill.color = 0xff191919; | |||
| if (hasDarkMode) | |||
| { | |||
| const ExtendedNSVGimage ext = { handle, shapesOrig, shapesDark }; | |||
| loadedSVGs.push_back(ext); | |||
| if (rack::settings::darkMode) | |||
| handle->shapes = shapesDark; | |||
| } | |||
| return handle; | |||
| } | |||
| return nullptr; | |||
| } | |||
| void nsvgDeleteCardinal(NSVGimage* const handle) | |||
| { | |||
| for (auto it = loadedSVGs.cbegin(), end = loadedSVGs.cend(); it != end; ++it) | |||
| { | |||
| const ExtendedNSVGimage& ext(*it); | |||
| if (ext.handle != handle) | |||
| continue; | |||
| // delete duplicated resources | |||
| for (NSVGshape *next, *shape = ext.shapesDark;;) | |||
| { | |||
| next = shape->next; | |||
| nsvg__deletePaint(&shape->fill); | |||
| nsvg__deletePaint(&shape->stroke); | |||
| std::free(shape); | |||
| if (next == nullptr) | |||
| break; | |||
| shape = next; | |||
| } | |||
| // revert shapes back to original | |||
| handle->shapes = ext.shapesOrig; | |||
| loadedSVGs.erase(it); | |||
| break; | |||
| } | |||
| nsvgDelete(handle); | |||
| } | |||
| void switchDarkMode(bool darkMode) | |||
| { | |||
| if (rack::settings::darkMode == darkMode) | |||
| return; | |||
| rack::settings::darkMode = darkMode; | |||
| for (ExtendedNSVGimage& ext : loadedSVGs) | |||
| ext.handle->shapes = darkMode ? ext.shapesDark : ext.shapesOrig; | |||
| } | |||
| @@ -40,6 +40,7 @@ | |||
| #include <ui/ProgressBar.hpp> | |||
| #include <ui/Label.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <widget/FramebufferWidget.hpp> | |||
| #include <window/Window.hpp> | |||
| #include <asset.hpp> | |||
| #include <context.hpp> | |||
| @@ -60,6 +61,8 @@ | |||
| # include "DistrhoStandaloneUtils.hpp" | |||
| #endif | |||
| void switchDarkMode(bool darkMode); | |||
| namespace rack { | |||
| namespace asset { | |||
| std::string patchesPath(); | |||
| @@ -492,6 +495,20 @@ struct KnobScrollSensitivitySlider : ui::Slider { | |||
| }; | |||
| static void setAllFramebufferWidgetsDirty(widget::Widget* const widget) | |||
| { | |||
| for (widget::Widget* child : widget->children) | |||
| { | |||
| if (widget::FramebufferWidget* const fbw = dynamic_cast<widget::FramebufferWidget*>(child)) | |||
| { | |||
| fbw->setDirty(); | |||
| break; | |||
| } | |||
| setAllFramebufferWidgetsDirty(child); | |||
| } | |||
| } | |||
| struct ViewButton : MenuButton { | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| @@ -500,6 +517,14 @@ struct ViewButton : MenuButton { | |||
| menu->addChild(createMenuLabel("Appearance")); | |||
| std::string darkModeText; | |||
| if (settings::darkMode) | |||
| darkModeText = CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Dark Mode", darkModeText, []() { | |||
| switchDarkMode(!settings::darkMode); | |||
| setAllFramebufferWidgetsDirty(APP->scene); | |||
| })); | |||
| menu->addChild(createBoolPtrMenuItem("Show tooltips", "", &settings::tooltips)); | |||
| ZoomSlider* zoomSlider = new ZoomSlider; | |||
| @@ -563,10 +588,10 @@ struct ViewButton : MenuButton { | |||
| #ifdef DISTRHO_OS_WASM | |||
| const bool fullscreen = APP->window->isFullScreen(); | |||
| std::string fullscreenText = "F11"; | |||
| if (fullscreen) | |||
| fullscreenText += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() { | |||
| std::string rightText = "F11"; | |||
| if (rightText) | |||
| rightText += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Fullscreen", rightText, [=]() { | |||
| APP->window->setFullScreen(!fullscreen); | |||
| })); | |||
| #endif | |||
| @@ -612,10 +637,10 @@ struct EngineButton : MenuButton { | |||
| #ifdef DISTRHO_OS_WASM | |||
| if (supportsAudioInput()) { | |||
| const bool enabled = isAudioInputEnabled(); | |||
| std::string text = "Enable Audio Input"; | |||
| std::string rightText; | |||
| if (enabled) | |||
| text += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem(text, "", [enabled]() { | |||
| rightText = CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Enable Audio Input", rightText, [enabled]() { | |||
| if (!enabled) | |||
| requestAudioInput(); | |||
| })); | |||
| @@ -623,10 +648,10 @@ struct EngineButton : MenuButton { | |||
| if (supportsMIDI()) { | |||
| const bool enabled = isMIDIEnabled(); | |||
| std::string text = "Enable MIDI"; | |||
| std::string rightText; | |||
| if (enabled) | |||
| text += " " CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem(text, "", [enabled]() { | |||
| rightText = CHECKMARK_STRING; | |||
| menu->addChild(createMenuItem("Enable MIDI", rightText, [enabled]() { | |||
| if (!enabled) | |||
| requestMIDI(); | |||
| })); | |||