From 187b1c72dd4c0c672acedba39db60c7e2739c7bf Mon Sep 17 00:00:00 2001 From: falkTX Date: Tue, 19 Jul 2022 16:21:45 +0100 Subject: [PATCH] Introduce dark/light mode switch, EXPERIMENTAL --- deps/PawPaw | 2 +- include/settings.hpp | 29 +++++ plugins/Cardinal/src/HostTime.cpp | 18 ++-- plugins/Cardinal/src/ModuleWidgets.hpp | 15 ++- plugins/Cardinal/src/glBars.cpp | 17 +-- plugins/ImpromptuModularDark/PanelTheme.cpp | 7 +- src/CardinalCommon.cpp | 2 + src/CardinalCommon.hpp | 4 - src/Makefile | 1 + src/custom/dep.cpp | 111 +++++++++++++++++++- src/override/MenuBar.cpp | 45 ++++++-- 11 files changed, 202 insertions(+), 49 deletions(-) create mode 100644 include/settings.hpp diff --git a/deps/PawPaw b/deps/PawPaw index ae5e265..18d8835 160000 --- a/deps/PawPaw +++ b/deps/PawPaw @@ -1 +1 @@ -Subproject commit ae5e26516596024a0268b5f8f1685050a248875a +Subproject commit 18d8835bd17da6f651a49d83f8d911d7c1858399 diff --git a/include/settings.hpp b/include/settings.hpp new file mode 100644 index 0000000..49baef7 --- /dev/null +++ b/include/settings.hpp @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include_next "settings.hpp" + +namespace rack { +namespace settings { + +extern bool darkMode; +extern int rateLimit; + +} // namespace settings +} // namespace rack diff --git a/plugins/Cardinal/src/HostTime.cpp b/plugins/Cardinal/src/HostTime.cpp index 418c529..ebb610c 100644 --- a/plugins/Cardinal/src/HostTime.cpp +++ b/plugins/Cardinal/src/HostTime.cpp @@ -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(Vec(RACK_GRID_WIDTH, 0))); - addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); - addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); - addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + createAndAddScrews(); addOutput(createOutput(Vec(startX, startY_cv + 0 * padding), m, HostTime::kHostTimeRolling)); addOutput(createOutput(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); diff --git a/plugins/Cardinal/src/ModuleWidgets.hpp b/plugins/Cardinal/src/ModuleWidgets.hpp index e97772e..2045540 100644 --- a/plugins/Cardinal/src/ModuleWidgets.hpp +++ b/plugins/Cardinal/src/ModuleWidgets.hpp @@ -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; diff --git a/plugins/Cardinal/src/glBars.cpp b/plugins/Cardinal/src/glBars.cpp index 979f9f5..190a77d 100644 --- a/plugins/Cardinal/src/glBars.cpp +++ b/plugins/Cardinal/src/glBars.cpp @@ -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(Vec(RACK_GRID_WIDTH, 0))); - addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); - addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); - addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + createAndAddScrews(); addInput(createInput(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 diff --git a/plugins/ImpromptuModularDark/PanelTheme.cpp b/plugins/ImpromptuModularDark/PanelTheme.cpp index d2dfcc1..6106d31 100644 --- a/plugins/ImpromptuModularDark/PanelTheme.cpp +++ b/plugins/ImpromptuModularDark/PanelTheme.cpp @@ -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); } } - diff --git a/src/CardinalCommon.cpp b/src/CardinalCommon.cpp index d42bb5a..126525b 100644 --- a/src/CardinalCommon.cpp +++ b/src/CardinalCommon.cpp @@ -30,6 +30,7 @@ #include "AsyncDialog.hpp" #include "PluginContext.hpp" #include "DistrhoPluginUtils.hpp" +#include "settings.hpp" #include #include @@ -61,6 +62,7 @@ const std::string CARDINAL_VERSION = "22.07"; namespace rack { namespace settings { +bool darkMode = true; int rateLimit = 0; } diff --git a/src/CardinalCommon.hpp b/src/CardinalCommon.hpp index 03c305d..6ecc4c5 100644 --- a/src/CardinalCommon.hpp +++ b/src/CardinalCommon.hpp @@ -33,10 +33,6 @@ extern const std::string CARDINAL_VERSION; namespace rack { -namespace settings { -extern int rateLimit; -} - namespace ui { struct Menu; } diff --git a/src/Makefile b/src/Makefile index f575c33..f632812 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/custom/dep.cpp b/src/custom/dep.cpp index fd61147..b250136 100644 --- a/src/custom/dep.cpp +++ b/src/custom/dep.cpp @@ -19,6 +19,13 @@ #include #include +#include + +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 @@ -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 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(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(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(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; +} diff --git a/src/override/MenuBar.cpp b/src/override/MenuBar.cpp index 327a360..711e765 100644 --- a/src/override/MenuBar.cpp +++ b/src/override/MenuBar.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -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(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(); }));