From 13020e793d5955d1286b455977d92941b31e2a90 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 25 Jul 2022 15:21:54 +0100 Subject: [PATCH] Add a few more things for light/dark mode switch, WIP --- src/custom/asset.cpp | 3 - src/custom/dep.cpp | 412 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 353 insertions(+), 62 deletions(-) diff --git a/src/custom/asset.cpp b/src/custom/asset.cpp index 846af5b..83dacf5 100644 --- a/src/custom/asset.cpp +++ b/src/custom/asset.cpp @@ -50,9 +50,6 @@ std::string user(std::string filename) { // get system resource, trimming "res/" prefix if we are loaded as a plugin bundle std::string system(std::string filename) { - // Always use dark screws - if (string::endsWith(filename, "/ScrewSilver.svg")) - filename = filename.substr(0, filename.size()-10) + "Black.svg"; return system::join(systemDir, bundlePath.empty() ? filename : trim(filename)); } diff --git a/src/custom/dep.cpp b/src/custom/dep.cpp index 6934dd6..44f0942 100644 --- a/src/custom/dep.cpp +++ b/src/custom/dep.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace rack { namespace settings { @@ -60,7 +61,7 @@ static const struct { const char* const filename; const char* shapeIdsToIgnore[5]; const int shapeNumberToIgnore; -} svgFilesToInvert[] = { +} svgFilesToInvertForDarkMode[] = { // MIT { "/21kHz/res/Panels/D_Inf.svg", {}, -1 }, { "/21kHz/res/Panels/PalmLoop.svg", {}, -1 }, @@ -329,7 +330,150 @@ static const struct { { "/WhatTheRack/res/WhatTheRack.svg", {}, -1 }, }; -static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const char* const svgFileToInvert = nullptr) +static const struct { + const char* const filename; + const char* shapeIdsToIgnore[5]; + const int shapeNumberToIgnore; +} svgFilesToInvertForLightMode[] = { + // GPLv3+ + /* FIXME does not work very well + { "/Autinn/res/AmpModule.svg", {}, -1 }, + { "/Autinn/res/BassModule.svg", {}, -1 }, + { "/Autinn/res/CVConverterModule.svg", {}, -1 }, + { "/Autinn/res/ComponentLibrary", {}, -1 }, + { "/Autinn/res/DeadbandModule.svg", {}, -1 }, + { "/Autinn/res/DigiModule.svg", {}, -1 }, + { "/Autinn/res/DiseeModule.svg", {}, -1 }, + { "/Autinn/res/FilModule.svg", {}, -1 }, + { "/Autinn/res/FlopperModule.svg", {}, -1 }, + { "/Autinn/res/ImpModule.svg", {}, -1 }, + { "/Autinn/res/JetteModule.svg", {}, -1 }, + { "/Autinn/res/MelodyModule.svg", {}, -1 }, + { "/Autinn/res/MeraModule.svg", {}, -1 }, + { "/Autinn/res/Mixer6Module.svg", {}, -1 }, + { "/Autinn/res/NapModule.svg", {}, -1 }, + { "/Autinn/res/NonModule.svg", {}, -1 }, + { "/Autinn/res/OxcartModule.svg", {}, -1 }, + { "/Autinn/res/RebelModule.svg", {}, -1 }, + { "/Autinn/res/RetriModule.svg", {}, -1 }, + { "/Autinn/res/SawModule.svg", {}, -1 }, + { "/Autinn/res/SjipModule.svg", {}, -1 }, + { "/Autinn/res/SquareModule.svg", {}, -1 }, + { "/Autinn/res/VibratoModule.svg", {}, -1 }, + { "/Autinn/res/VxyModule.svg", {}, -1 }, + { "/Autinn/res/ZodModule.svg", {}, -1 }, + */ + // ??? used for testing, might get turned off + { "/Befaco/res/panels/ABC.svg", {}, -1 }, + { "/Befaco/res/panels/ADSR.svg", {}, -1 }, + { "/Befaco/res/panels/ChoppingKinky.svg", {}, -1 }, + { "/Befaco/res/panels/DualAtenuverter.svg", {}, -1 }, + { "/Befaco/res/panels/EvenVCO.svg", {}, -1 }, + { "/Befaco/res/panels/HexmixVCA.svg", {}, -1 }, + { "/Befaco/res/panels/Kickall.svg", {}, -1 }, + { "/Befaco/res/panels/Mex.svg", {}, -1 }, + { "/Befaco/res/panels/Mixer.svg", {}, -1 }, + { "/Befaco/res/panels/Morphader.svg", {}, -1 }, + { "/Befaco/res/panels/Muxlicer.svg", {}, -1 }, + { "/Befaco/res/panels/NoisePlethora.svg", {}, -1 }, + { "/Befaco/res/panels/Percall.svg", {}, -1 }, + { "/Befaco/res/panels/Rampage.svg", {}, -1 }, + { "/Befaco/res/panels/STMix.svg", {}, -1 }, + { "/Befaco/res/panels/SamplingModulator.svg", {}, -1 }, + { "/Befaco/res/panels/SlewLimiter.svg", {}, -1 }, + { "/Befaco/res/panels/SpringReverb.svg", {}, -1 }, + { "/Befaco/res/panels/StereoStrip.svg", {}, -1 }, + // GPLv3+ + { "/Cardinal/res/AudioFile.svg", {}, -1 }, + { "/Cardinal/res/AudioToCVPitch.svg", {}, -1 }, + { "/Cardinal/res/Carla.svg", {}, -1 }, + { "/Cardinal/res/ExpanderMIDI.svg", {}, -1 }, + { "/Cardinal/res/glBars.svg", {}, -1 }, + { "/Cardinal/res/HostAudio.svg", {}, -1 }, + { "/Cardinal/res/HostCV.svg", {}, -1 }, + { "/Cardinal/res/HostMIDI.svg", {}, -1 }, + { "/Cardinal/res/HostMIDICC.svg", {}, -1 }, + { "/Cardinal/res/HostMIDIGate.svg", {}, -1 }, + { "/Cardinal/res/HostMIDIMap.svg", {}, -1 }, + { "/Cardinal/res/HostParameters.svg", {}, -1 }, + { "/Cardinal/res/HostParamsMap.svg", {}, -1 }, + { "/Cardinal/res/HostTime.svg", {}, -1 }, + { "/Cardinal/res/Ildaeil.svg", {}, -1 }, + // GPLv3+ + { "/forsitan-modulare/res/alea.svg", {}, -1 }, + { "/forsitan-modulare/res/cumuli.svg", {}, -1 }, + { "/forsitan-modulare/res/deinde.svg", {}, -1 }, + { "/forsitan-modulare/res/interea.svg", {}, -1 }, + { "/forsitan-modulare/res/palette.svg", {}, -1 }, + { "/forsitan-modulare/res/pavo.svg", {}, -1 }, + // GPLv3+ + /* FIXME ends up transparent?? + { "/Fundamental/res/8vert.svg", {}, -1 }, + { "/Fundamental/res/ADSR.svg", {}, -1 }, + { "/Fundamental/res/Delay.svg", {}, -1 }, + { "/Fundamental/res/LFO.svg", {}, -1 }, + { "/Fundamental/res/Merge.svg", {}, -1 }, + { "/Fundamental/res/MidSide.svg", {}, -1 }, + { "/Fundamental/res/Mixer.svg", {}, -1 }, + { "/Fundamental/res/Mutes.svg", {}, -1 }, + { "/Fundamental/res/Noise.svg", {}, -1 }, + { "/Fundamental/res/Octave.svg", {}, -1 }, + { "/Fundamental/res/Pulses.svg", {}, -1 }, + { "/Fundamental/res/Quantizer.svg", {}, -1 }, + { "/Fundamental/res/Random.svg", {}, -1 }, + { "/Fundamental/res/SEQ3.svg", {}, -1 }, + { "/Fundamental/res/Scope.svg", {}, -1 }, + { "/Fundamental/res/SequentialSwitch1.svg", {}, -1 }, + { "/Fundamental/res/SequentialSwitch2.svg", {}, -1 }, + { "/Fundamental/res/Split.svg", {}, -1 }, + { "/Fundamental/res/Sum.svg", {}, -1 }, + { "/Fundamental/res/VCA-1.svg", {}, -1 }, + { "/Fundamental/res/VCA.svg", {}, -1 }, + { "/Fundamental/res/VCF.svg", {}, -1 }, + { "/Fundamental/res/VCMixer.svg", {}, -1 }, + { "/Fundamental/res/VCO.svg", {}, -1 }, + { "/Fundamental/res/WTLFO.svg", {}, -1 }, + { "/Fundamental/res/WTVCO.svg", {}, -1 }, + */ + // MIT + { "/HamptonHarmonics/res/Arp.svg", {}, -1 }, + { "/HamptonHarmonics/res/Progress.svg", {}, -1 }, + // GPLv3+ + { "/LomasModules/res/AdvancedSampler.svg", {}, -1 }, + { "/LomasModules/res/GateSequencer.svg", {}, -1 }, + // GPLv3+ + { "/sonusmodular/res/addiction.svg", {}, -1 }, + { "/sonusmodular/res/bitter.svg", {}, -1 }, + { "/sonusmodular/res/bymidside.svg", {}, -1 }, + { "/sonusmodular/res/campione.svg", {}, -1 }, + { "/sonusmodular/res/chainsaw.svg", {}, -1 }, + { "/sonusmodular/res/ctrl.svg", {}, -1 }, + { "/sonusmodular/res/deathcrush.svg", {}, -1 }, + { "/sonusmodular/res/fraction.svg", {}, -1 }, + { "/sonusmodular/res/harmony.svg", {}, -1 }, + { "/sonusmodular/res/ladrone.svg", {}, -1 }, + { "/sonusmodular/res/luppolo.svg", {}, -1 }, + { "/sonusmodular/res/luppolo3.svg", {}, -1 }, + { "/sonusmodular/res/micromacro.svg", {}, -1 }, + { "/sonusmodular/res/mrcheb.svg", {}, -1 }, + { "/sonusmodular/res/multimulti.svg", {}, -1 }, + { "/sonusmodular/res/neurosc.svg", {}, -1 }, + { "/sonusmodular/res/oktagon.svg", {}, -1 }, + { "/sonusmodular/res/osculum.svg", {}, -1 }, + { "/sonusmodular/res/paramath.svg", {}, -1 }, + { "/sonusmodular/res/piconoise.svg", {}, -1 }, + { "/sonusmodular/res/pith.svg", {}, -1 }, + { "/sonusmodular/res/pusher.svg", {}, -1 }, + { "/sonusmodular/res/ringo.svg", {}, -1 }, + { "/sonusmodular/res/scramblase.svg", {}, -1 }, + { "/sonusmodular/res/tropicana.svg", {}, -1 }, + { "/sonusmodular/res/twoff.svg", {}, -1 }, + { "/sonusmodular/res/yabp.svg", {}, -1 }, + // TODO bacon, chowdsp, ??? +}; + +static inline +bool invertPaintForDarkMode(NSVGshape* const shape, NSVGpaint& paint, const char* const svgFileToInvert = nullptr) { if (paint.type == NSVG_PAINT_LINEAR_GRADIENT && svgFileToInvert != nullptr) { @@ -601,6 +745,16 @@ static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const c } } +static inline +bool invertPaintForLightMode(NSVGshape* const shape, NSVGpaint& paint) +{ + paint.color = (paint.color & 0xff000000) + | (0xff0000 - (paint.color & 0xff0000)) + | (0xff00 - (paint.color & 0xff00)) + | (0xff - (paint.color & 0xff)); + return true; +} + extern "C" { NSVGimage* nsvgParseFromFileCardinal(const char* filename, const char* units, float dpi); void nsvgDeleteCardinal(NSVGimage*); @@ -608,17 +762,86 @@ void nsvgDeleteCardinal(NSVGimage*); struct ExtendedNSVGimage { NSVGimage* handle; + NSVGimage* handleOrig; + NSVGimage* handleMOD; NSVGshape* shapesOrig; - NSVGshape* shapesDark; + NSVGshape* shapesMOD; }; -static std::list loadedSVGs; -static void nsvg__duplicatePaint(NSVGpaint& dst, NSVGpaint& src) +static std::list loadedDarkSVGs; +static std::list loadedLightSVGs; + +static inline +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)); + const size_t size = sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(src.gradient->nstops-1); + dst.gradient = static_cast(malloc(size)); + std::memcpy(dst.gradient, src.gradient, size); + } +} + +static inline +NSVGshape* nsvg__duplicateShapes(NSVGshape* const orig) +{ + NSVGshape* const dup = static_cast(malloc(sizeof(NSVGshape))); + std::memcpy(dup, orig, sizeof(NSVGshape)); + nsvg__duplicatePaint(dup->fill, orig->fill); + nsvg__duplicatePaint(dup->stroke, orig->stroke); + + for (NSVGshape* shape2 = dup;;) + { + 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; + } + + return dup; +} + +static inline +void deleteExtendedNSVGimage(ExtendedNSVGimage& ext) +{ + if (ext.shapesMOD != nullptr) + { + // delete duplicated resources + for (NSVGshape *next, *shape = ext.shapesMOD;;) + { + 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 + ext.handle->shapes = ext.shapesOrig; + ext.shapesMOD = nullptr; + } + + if (ext.handleMOD != nullptr) + { + nsvgDelete(ext.handleMOD); + ext.handleMOD = nullptr; + } + + if (ext.handleOrig != nullptr) + { + std::memcpy(ext.handle, ext.handleOrig, sizeof(NSVGimage)); + std::free(ext.handleOrig); + ext.handleOrig = nullptr; } } @@ -626,14 +849,36 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con { if (NSVGimage* const handle = nsvgParseFromFile(filename, units, dpi)) { + const size_t filenamelen = std::strlen(filename); + bool hasDarkMode = false; + bool hasLightMode = false; + NSVGimage* handleMOD = nullptr; NSVGshape* shapesOrig; - NSVGshape* shapesDark; + NSVGshape* shapesMOD; - for (size_t i = 0; i < sizeof(svgFilesToInvert)/sizeof(svgFilesToInvert[0]); ++i) + // Special case for light/dark screws + if (std::strncmp(filename + (filenamelen-16), "/ScrewSilver.svg", 16) == 0) { - const char* const svgFileToInvert = svgFilesToInvert[i].filename; - const size_t filenamelen = std::strlen(filename); + const std::string blackfilename = std::string(filename).substr(0, filenamelen-10) + "Black.svg"; + hasDarkMode = true; + shapesOrig = shapesMOD = nullptr; + handleMOD = nsvgParseFromFile(blackfilename.c_str(), units, dpi); + goto postparse; + } + + if (std::strncmp(filename + (filenamelen-15), "/ScrewBlack.svg", 15) == 0) + { + const std::string silverfilename = std::string(filename).substr(0, filenamelen-9) + "Silver.svg"; + hasLightMode = true; + shapesOrig = shapesMOD = nullptr; + handleMOD = nsvgParseFromFile(silverfilename.c_str(), units, dpi); + goto postparse; + } + + for (size_t i = 0; i < sizeof(svgFilesToInvertForDarkMode)/sizeof(svgFilesToInvertForDarkMode[0]); ++i) + { + const char* const svgFileToInvert = svgFilesToInvertForDarkMode[i].filename; const size_t filterlen = std::strlen(svgFileToInvert); if (filenamelen < filterlen) @@ -641,34 +886,17 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con if (std::strncmp(filename + (filenamelen-filterlen), svgFileToInvert, filterlen) != 0) continue; - const char* const* const shapeIdsToIgnore = svgFilesToInvert[i].shapeIdsToIgnore; - const int shapeNumberToIgnore = svgFilesToInvert[i].shapeNumberToIgnore; + const char* const* const shapeIdsToIgnore = svgFilesToInvertForDarkMode[i].shapeIdsToIgnore; + const int shapeNumberToIgnore = svgFilesToInvertForDarkMode[i].shapeNumberToIgnore; int shapeCounter = 0; hasDarkMode = true; + handleMOD = nullptr; 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; - } + shapesMOD = nsvg__duplicateShapes(shapesOrig); // shape paint inversion - for (NSVGshape* shape = shapesDark; shape != nullptr; shape = shape->next, ++shapeCounter) + for (NSVGshape* shape = shapesMOD; shape != nullptr; shape = shape->next, ++shapeCounter) { if (shapeNumberToIgnore == shapeCounter) continue; @@ -685,11 +913,37 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con if (ignore) continue; - if (invertPaint(shape, shape->fill, svgFileToInvert)) - invertPaint(shape, shape->stroke, svgFileToInvert); + if (invertPaintForDarkMode(shape, shape->fill, svgFileToInvert)) + invertPaintForDarkMode(shape, shape->stroke, svgFileToInvert); } - break; + goto postparse; + } + + for (size_t i = 0; i < sizeof(svgFilesToInvertForLightMode)/sizeof(svgFilesToInvertForLightMode[0]); ++i) + { + const char* const svgFileToInvert = svgFilesToInvertForLightMode[i].filename; + const size_t filenamelen = std::strlen(filename); + const size_t filterlen = std::strlen(svgFileToInvert); + + if (filenamelen < filterlen) + continue; + if (std::strncmp(filename + (filenamelen-filterlen), svgFileToInvert, filterlen) != 0) + continue; + + hasLightMode = true; + handleMOD = nullptr; + shapesOrig = handle->shapes; + shapesMOD = nsvg__duplicateShapes(shapesOrig); + + // shape paint inversion + for (NSVGshape* shape = shapesMOD; shape != nullptr; shape = shape->next) + { + if (invertPaintForLightMode(shape, shape->fill)) + invertPaintForLightMode(shape, shape->stroke); + } + + goto postparse; } // Special case for AmalgamatedHarmonics background color @@ -697,13 +951,45 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con if (std::strstr(filename, "/AmalgamatedHarmonics/") != nullptr) handle->shapes->fill.color = 0xff191919; +postparse: + NSVGimage* handleOrig; + + if (handleMOD != nullptr) + { + handleOrig = static_cast(malloc(sizeof(NSVGimage))); + std::memcpy(handleOrig, handle, sizeof(NSVGimage)); + } + else + { + handleOrig = nullptr; + } + if (hasDarkMode) { - const ExtendedNSVGimage ext = { handle, shapesOrig, shapesDark }; - loadedSVGs.push_back(ext); + const ExtendedNSVGimage ext = { handle, handleOrig, handleMOD, shapesOrig, shapesMOD }; + loadedDarkSVGs.push_back(ext); if (rack::settings::darkMode) - handle->shapes = shapesDark; + { + if (shapesMOD != nullptr) + handle->shapes = shapesMOD; + else if (handleMOD != nullptr) + std::memcpy(handle, handleMOD, sizeof(NSVGimage)); + } + } + + if (hasLightMode) + { + const ExtendedNSVGimage ext = { handle, handleOrig, handleMOD, shapesOrig, shapesMOD }; + loadedLightSVGs.push_back(ext); + + if (!rack::settings::darkMode) + { + if (shapesMOD != nullptr) + handle->shapes = shapesMOD; + else if (handleMOD != nullptr) + std::memcpy(handle, handleMOD, sizeof(NSVGimage)); + } } return handle; @@ -714,32 +1000,27 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con void nsvgDeleteCardinal(NSVGimage* const handle) { - for (auto it = loadedSVGs.cbegin(), end = loadedSVGs.cend(); it != end; ++it) + for (auto it = loadedDarkSVGs.begin(), end = loadedDarkSVGs.end(); it != end; ++it) { - const ExtendedNSVGimage& ext(*it); + 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; + deleteExtendedNSVGimage(ext); + loadedDarkSVGs.erase(it); + break; + } - shape = next; - } + for (auto it = loadedLightSVGs.begin(), end = loadedLightSVGs.end(); it != end; ++it) + { + ExtendedNSVGimage& ext(*it); - // revert shapes back to original - handle->shapes = ext.shapesOrig; + if (ext.handle != handle) + continue; - loadedSVGs.erase(it); + deleteExtendedNSVGimage(ext); + loadedLightSVGs.erase(it); break; } @@ -753,6 +1034,19 @@ void switchDarkMode(bool darkMode) rack::settings::darkMode = darkMode; - for (ExtendedNSVGimage& ext : loadedSVGs) - ext.handle->shapes = darkMode ? ext.shapesDark : ext.shapesOrig; + for (ExtendedNSVGimage& ext : loadedDarkSVGs) + { + if (ext.shapesMOD != nullptr) + ext.handle->shapes = darkMode ? ext.shapesMOD : ext.shapesOrig; + else if (ext.handleMOD != nullptr) + std::memcpy(ext.handle, darkMode ? ext.handleMOD : ext.handleOrig, sizeof(NSVGimage)); + } + + for (ExtendedNSVGimage& ext : loadedLightSVGs) + { + if (ext.shapesMOD != nullptr) + ext.handle->shapes = darkMode ? ext.shapesOrig : ext.shapesMOD; + else if (ext.handleMOD != nullptr) + std::memcpy(ext.handle, darkMode ? ext.handleOrig : ext.handleMOD, sizeof(NSVGimage)); + } }